Compare commits

...
9 Commits
Author SHA1 Message Date
fyears 5c56f2146f 0.5.21
Release A New Version / build (20.x) (push) Failing after 41s
2024-09-09 00:13:09 +08:00
fyears 74125f8dff fix config dir special empty condiction 2024-09-09 00:12:38 +08:00
fyears ace16de20c add complex logic to allow list and ignore list 2024-09-09 00:00:12 +08:00
fyears 716e5aa6c5 not detect chars in name for now 2024-09-07 15:26:19 +08:00
fyears fcdbb71493 add star and download history 2024-09-01 18:53:19 +08:00
fyears 0040e18b49 fix link 2024-09-01 18:30:31 +08:00
fyears b5d56843fd bump to 0.5.20
Release A New Version / build (20.x) (push) Failing after 52s
2024-09-01 18:28:46 +08:00
fyears b63b6791aa re-enable profiler 2024-09-01 18:28:08 +08:00
Matvey Romanov 2a3d1265b3 Update en.json (#794) 2024-09-01 17:34:46 +08:00
17 changed files with 450 additions and 65 deletions
+13 -1
View File
@@ -201,8 +201,20 @@ See [PRO](./docs/pro/README.md) for more details.
## How To Debug ## How To Debug
See [here](./docs/how_to_debug/README.md) for more details. If you see any errors, please check the doc [here](./docs/how_to_debug/README.md) for more details.
Moreover, sometimes the program runs but slowly, you want to check the performance by [enabling the profiler](./docs/check_performance/README.md).
## Bonus: Import And Export Not-Oauth2 Plugin Settings By QR Code ## Bonus: Import And Export Not-Oauth2 Plugin Settings By QR Code
See [here](./docs/import_export_some_settings.md) for more details. See [here](./docs/import_export_some_settings.md) for more details.
## Download History
Download history can be viewed on the unofficial [Obsidian Stats](https://www.moritzjung.dev/obsidian-stats/plugins/remotely-save/#downloads) (NOT affiliated with official Obsidian and GitHub and Remotely Save).
## Star History
(NOT affiliated with official Obsidian and GitHub and Remotely Save.)
[![Star History Chart](https://api.star-history.com/svg?repos=remotely-save/remotely-save&type=Date)](https://star-history.com/#remotely-save/remotely-save&Date)
+13 -2
View File
@@ -199,11 +199,22 @@ PRO(付费)功能“智能冲突”为用户提供了另一个选项:合
详见[PRO](./docs/pro/README.md)了解更多详情。 详见[PRO](./docs/pro/README.md)了解更多详情。
## 如何调试 ## 如何调试debug
详见[这里](./docs/how_to_debug/README.md)了解更多详情 如发生错误,查看[这里文档](./docs/how_to_debug/README.md)了解调试方式
如果没有发生错误,但是运行起来很慢,你需要[开启“性能收集”](./docs/check_performance/README)来看看有没有哪一步特别慢。
## 额外功能:通过 QR 码导入和导出非 OAuth2 插件设置 ## 额外功能:通过 QR 码导入和导出非 OAuth2 插件设置
详见[这里](./docs/import_export_some_settings.md)了解更多详情。 详见[这里](./docs/import_export_some_settings.md)了解更多详情。
## 下载历史
下载历史可以从非官方的 [Obsidian Stats](https://www.moritzjung.dev/obsidian-stats/plugins/remotely-save/#downloads) 查阅。(和官方 ObsidianGitHubRemotely Save 均无利益关系。)
## 星星历史
(和官方 ObsidianGitHubRemotely Save 均无利益关系。)
[![星星历史 曲线图](https://api.star-history.com/svg?repos=remotely-save/remotely-save&type=Date)](https://star-history.com/#remotely-save/remotely-save&Date)
+13
View File
@@ -0,0 +1,13 @@
# Check performance
1. Go to settings, scroll to the very end, and enable the "Enable Profiler" option.
2. Also enable "Enable Profiler Printing".
3. Check Console Output (directly or via `vConsole` plugin). More details are [here](../how_to_debug/README.md).
4. Sync!
5. You can also "Export Profiler Results" afterwards. A new file `_debug_remotely_save/profiler_results_exported_on_xxxx.md` will be generated.
In the console or exported files, you can see the time cost of each steps.
![profiler settings](./profiler_settings.png)
![profiler exported](./profiler_exported.png)
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:5a71d0746a75c93aa4cb5be3f7964bfdf74722a10505f5a1965790738eaebdc4
size 265289
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:4ffd1557c2f714c56bfacd8166b85749d6bfa15c2f9c7363942b443d51288083
size 81259
+1 -1
View File
@@ -1,7 +1,7 @@
{ {
"id": "remotely-save", "id": "remotely-save",
"name": "Remotely Save", "name": "Remotely Save",
"version": "0.5.19", "version": "0.5.21",
"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
View File
@@ -1,7 +1,7 @@
{ {
"id": "remotely-save", "id": "remotely-save",
"name": "Remotely Save", "name": "Remotely Save",
"version": "0.5.19", "version": "0.5.21",
"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
View File
@@ -1,6 +1,6 @@
{ {
"name": "remotely-save", "name": "remotely-save",
"version": "0.5.19", "version": "0.5.21",
"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",
+200 -51
View File
@@ -29,6 +29,7 @@ import {
import { import {
atWhichLevel, atWhichLevel,
checkValidName, checkValidName,
getFolderLevels,
getParentFolder, getParentFolder,
isHiddenPath, isHiddenPath,
isSpecialFolderNameToSkip, isSpecialFolderNameToSkip,
@@ -139,7 +140,14 @@ const isBookmarksFile = (x: string, configDir: string) => {
); );
}; };
const isSkipItemByName = ( interface IsSkipResult {
enableAllowMode: boolean;
isExplictlyAllowed: boolean;
isExplictlyIgnored: boolean;
finalIsIgnored: boolean;
}
export const checkIsSkipItemOrNotByName = (
key: string, key: string,
syncConfigDir: boolean, syncConfigDir: boolean,
syncBookmarks: boolean, syncBookmarks: boolean,
@@ -147,13 +155,15 @@ const isSkipItemByName = (
configDir: string, configDir: string,
ignorePaths: string[], ignorePaths: string[],
onlyAllowPaths: string[] onlyAllowPaths: string[]
) => { ): IsSkipResult => {
if (key === undefined) { if (key === undefined) {
throw Error(`isSkipItemByName meets undefinded key!`); throw Error(`checkIsSkipItemOrNotByName meets undefinded key!`);
} }
let finalIsIgnored: boolean | undefined = undefined;
let enableAllowMode = false; let enableAllowMode = false;
let isInAllowList = false; let isExplictlyAllowed = false;
if (onlyAllowPaths !== undefined && onlyAllowPaths.length > 0) { if (onlyAllowPaths !== undefined && onlyAllowPaths.length > 0) {
for (const r of onlyAllowPaths) { for (const r of onlyAllowPaths) {
if (r.trim() === "") { if (r.trim() === "") {
@@ -163,7 +173,7 @@ const isSkipItemByName = (
enableAllowMode = true; // we really want to check the allow list enableAllowMode = true; // we really want to check the allow list
if (XRegExp(r, "A").test(key)) { if (XRegExp(r, "A").test(key)) {
isInAllowList = true; isExplictlyAllowed = true;
} }
} }
} }
@@ -173,10 +183,11 @@ const isSkipItemByName = (
// and is deferred to next checking steps // and is deferred to next checking steps
// if the key doesn't meet the allow list, // if the key doesn't meet the allow list,
// it must be skippable. // it must be skippable.
if (enableAllowMode && !isInAllowList) { if (enableAllowMode && !isExplictlyAllowed) {
return true; // must be skippable finalIsIgnored = true; // must be skippable
} }
let isExplictlyIgnored = false;
if (ignorePaths !== undefined && ignorePaths.length > 0) { if (ignorePaths !== undefined && ignorePaths.length > 0) {
for (const r of ignorePaths) { for (const r of ignorePaths) {
if (r.trim() === "") { if (r.trim() === "") {
@@ -184,29 +195,141 @@ const isSkipItemByName = (
continue; continue;
} }
if (XRegExp(r, "A").test(key)) { if (XRegExp(r, "A").test(key)) {
return true; if (finalIsIgnored === undefined) {
isExplictlyIgnored = true;
finalIsIgnored = true;
}
} }
} }
} }
if (syncConfigDir && isInsideObsFolder(key, configDir)) { if (syncConfigDir && isInsideObsFolder(key, configDir)) {
return false; if (finalIsIgnored === undefined) {
finalIsIgnored = false;
}
} }
if (syncBookmarks && isBookmarksFile(key, configDir)) { if (syncBookmarks && isBookmarksFile(key, configDir)) {
return false; if (finalIsIgnored === undefined) {
finalIsIgnored = false;
}
} }
if (isSpecialFolderNameToSkip(key, [])) { if (isSpecialFolderNameToSkip(key, [])) {
// some special dirs and files are always skipped // some special dirs and files are always skipped
return true; if (finalIsIgnored === undefined) {
isExplictlyIgnored = true;
finalIsIgnored = true;
}
} }
return (
const checkIsHidden =
isHiddenPath(key, true, false) || isHiddenPath(key, true, false) ||
(!syncUnderscoreItems && isHiddenPath(key, false, true)) || (!syncUnderscoreItems && isHiddenPath(key, false, true)) ||
key === "/" || key === "/" ||
key === DEFAULT_FILE_NAME_FOR_METADATAONREMOTE || key === DEFAULT_FILE_NAME_FOR_METADATAONREMOTE ||
key === DEFAULT_FILE_NAME_FOR_METADATAONREMOTE2 key === DEFAULT_FILE_NAME_FOR_METADATAONREMOTE2;
); if (finalIsIgnored === undefined) {
isExplictlyIgnored = checkIsHidden;
finalIsIgnored = checkIsHidden;
}
if (finalIsIgnored === undefined) {
throw Error(`no finalIsIgnored in checkIsSkipItemOrNotByName for ${key}`);
}
return {
enableAllowMode: enableAllowMode,
isExplictlyAllowed: isExplictlyAllowed,
isExplictlyIgnored: isExplictlyIgnored,
finalIsIgnored: finalIsIgnored,
};
};
/**
* | finalIgnored | reason | explictlyIgnored | allowMode | explictlyAllowed |
* | ------------------------------- | -------------------------------------------- | ---------------- | --------- | ---------------- |
* | no | nothing blocking | no | no | no |
* | yes, MAY be changed by children | allow mode not allowed, inexplicitly ignored | no | yes | no |
* | no, MAY apply to parents | allow mode allowed | no | yes | yes |
* | yes, also apply to children | explictly ignored | yes | no | no |
* | yes, also apply to children | explictly ignored | yes | yes | no |
* | yes, also apply to children | explictly ignored | yes | yes | yes |
*/
export const getSkipItemsByList = (
skipOrNotResults: Record<string, IsSkipResult>,
ignorePaths: string[],
onlyAllowPaths: string[]
): string[] => {
const allPotentialKeys = Object.keys(skipOrNotResults);
if (
allPotentialKeys.length === 0 ||
(ignorePaths.length === 0 && onlyAllowPaths.length === 0)
) {
return [];
}
// from short(shadow) to long(deep) , ascending
const sortedKeys = allPotentialKeys.sort((k1, k2) => k1.length - k2.length);
// we deal with explicty ignored list firstly, apply them to children
const explictlyIgnoredSet = new Set<string>();
for (const key of sortedKeys) {
if (skipOrNotResults[key].isExplictlyIgnored) {
skipOrNotResults[key].finalIsIgnored = true;
explictlyIgnoredSet.add(key);
} else {
const parents = getFolderLevels(key, true).reverse();
for (const key2 of parents) {
if (explictlyIgnoredSet.has(key2)) {
skipOrNotResults[key].isExplictlyIgnored = true;
skipOrNotResults[key].finalIsIgnored = true;
explictlyIgnoredSet.add(key);
break;
}
}
}
}
// we deal with explictly allow list secondly, apply them to PARENTS if possible
const enableAllowMode = skipOrNotResults[allPotentialKeys[0]].enableAllowMode;
if (enableAllowMode) {
for (let index = 0; index < sortedKeys.length; index++) {
// reverse order, long(deep) to short(shadow), ascending
const key = sortedKeys[sortedKeys.length - index - 1];
if (
!skipOrNotResults[key].isExplictlyIgnored &&
skipOrNotResults[key].isExplictlyAllowed
) {
// the file is explictly allowed, and not explictly ignored by anywhere
// we allow all its parents!
const parents = getFolderLevels(key, true).reverse();
for (const key2 of parents) {
if (
key2 in skipOrNotResults &&
!skipOrNotResults[key2].isExplictlyIgnored &&
!explictlyIgnoredSet.has(key2)
) {
skipOrNotResults[key2].isExplictlyAllowed = true;
skipOrNotResults[key2].finalIsIgnored = false; // from ignored to allowed
} else {
throw Error(
`${key}'s parent ${key2} in abnormal state: ${JSON.stringify(skipOrNotResults[key2])}`
);
}
}
}
}
}
// get all finalIsIgnored
const result: string[] = [];
for (const key of sortedKeys) {
if (skipOrNotResults[key].finalIsIgnored) {
result.push(key);
}
}
return result;
}; };
export type SyncPlanType = Record<string, MixedEntity>; export type SyncPlanType = Record<string, MixedEntity>;
@@ -235,33 +358,39 @@ const ensembleMixedEnties = async (
const finalMappings: SyncPlanType = {}; const finalMappings: SyncPlanType = {};
const skipOrNotResults: Record<string, IsSkipResult> = {};
// remote has to be first // remote has to be first
let remoteMaySkipCountAndNotConfig = 0;
for (const remote of remoteEntityList) { for (const remote of remoteEntityList) {
const remoteCopied = ensureMTimeOfRemoteEntityValid( const remoteCopied = ensureMTimeOfRemoteEntityValid(
copyEntityAndFixTimeFormat(remote, serviceType) copyEntityAndFixTimeFormat(remote, serviceType)
); );
const key = remoteCopied.key!; const key = remoteCopied.key!;
if (
isSkipItemByName( const skipOrNot = checkIsSkipItemOrNotByName(
key, key,
syncConfigDir, syncConfigDir,
syncBookmarks, syncBookmarks,
syncUnderscoreItems, syncUnderscoreItems,
configDir, configDir,
ignorePaths, ignorePaths,
onlyAllowPaths onlyAllowPaths
) );
) { skipOrNotResults[key] = skipOrNot;
continue; if (skipOrNot.finalIsIgnored && !key.startsWith(configDir)) {
remoteMaySkipCountAndNotConfig += 1;
} }
const checkValidNameResult = checkValidName(key); // 20240907: users (not on windows) doesn't like it. revert back now.
if (!checkValidNameResult.result) { // TODO: platform specific but not introducing obsidian dependency into sync.ts
throw Error( // const checkValidNameResult = checkValidName(key);
`your remote folder/file name is invalid: ${checkValidNameResult.reason}` // if (!checkValidNameResult.result) {
); // throw Error(
} // `your remote folder/file name is invalid: ${checkValidNameResult.reason}`
// );
// }
finalMappings[key] = { finalMappings[key] = {
key: key, key: key,
@@ -272,19 +401,25 @@ const ensembleMixedEnties = async (
profiler?.insert("ensembleMixedEnties: finish remote"); profiler?.insert("ensembleMixedEnties: finish remote");
profiler?.insertSize("sizeof finalMappings", finalMappings); profiler?.insertSize("sizeof finalMappings", finalMappings);
if (Object.keys(finalMappings).length === 0 || localEntityList.length === 0) { if (
Object.keys(finalMappings).filter((k) => !k.startsWith(configDir)).length -
remoteMaySkipCountAndNotConfig ===
0 ||
localEntityList.filter((e) => !e.key?.startsWith(configDir)).length === 0
) {
// Special checking: // Special checking:
// if one side is totally empty, // if one side is totally empty,
// usually that's a hard rest. // usually that's a hard rest.
// So we need to ignore everything of prevSyncEntityList to avoid deletions! // So we need to ignore everything of prevSyncEntityList to avoid deletions!
// TODO: acutally erase everything of prevSyncEntityList? // TODO: acutally erase everything of prevSyncEntityList?
// TODO: local should also go through a isSkipItemByName checking beforehand // TODO: local should also go through a checkIsSkipItemOrNotByName checking beforehand
} else { } else {
// normally go through the prevSyncEntityList // normally go through the prevSyncEntityList
for (const prevSync of prevSyncEntityList) { for (const prevSync of prevSyncEntityList) {
const key = prevSync.key!; const key = prevSync.key!;
if (
isSkipItemByName( if (!(key in skipOrNotResults)) {
const skipOrNot = checkIsSkipItemOrNotByName(
key, key,
syncConfigDir, syncConfigDir,
syncBookmarks, syncBookmarks,
@@ -292,9 +427,8 @@ const ensembleMixedEnties = async (
configDir, configDir,
ignorePaths, ignorePaths,
onlyAllowPaths onlyAllowPaths
) );
) { skipOrNotResults[key] = skipOrNot;
continue;
} }
// TODO: abstraction leaking? // TODO: abstraction leaking?
@@ -320,8 +454,9 @@ const ensembleMixedEnties = async (
// (we don't consume prevSync here because it gains no benefit) // (we don't consume prevSync here because it gains no benefit)
for (const local of localEntityList) { for (const local of localEntityList) {
const key = local.key!; const key = local.key!;
if (
isSkipItemByName( if (!(key in skipOrNotResults)) {
const skipOrNot = checkIsSkipItemOrNotByName(
key, key,
syncConfigDir, syncConfigDir,
syncBookmarks, syncBookmarks,
@@ -329,17 +464,18 @@ const ensembleMixedEnties = async (
configDir, configDir,
ignorePaths, ignorePaths,
onlyAllowPaths onlyAllowPaths
) );
) { skipOrNotResults[key] = skipOrNot;
continue;
} }
const checkValidNameResult = checkValidName(key); // 20240907: users (not on windows) doesn't like it. revert back now.
if (!checkValidNameResult.result) { // TODO: platform specific but not introducing obsidian dependency into sync.ts
throw Error( // const checkValidNameResult = checkValidName(key);
`your local folder/file name is invalid: ${checkValidNameResult.reason}` // if (!checkValidNameResult.result) {
); // throw Error(
} // `your local folder/file name is invalid: ${checkValidNameResult.reason}`
// );
// }
// TODO: abstraction leaking? // TODO: abstraction leaking?
const localCopied = await fsEncrypt.encryptEntity( const localCopied = await fsEncrypt.encryptEntity(
@@ -358,6 +494,19 @@ const ensembleMixedEnties = async (
profiler?.insert("ensembleMixedEnties: finish local"); profiler?.insert("ensembleMixedEnties: finish local");
profiler?.insertSize("sizeof finalMappings", finalMappings); profiler?.insertSize("sizeof finalMappings", finalMappings);
// we check the skipOrNotResults again! in case we adjust some paths!
const allReallySkipKeys = getSkipItemsByList(
skipOrNotResults,
ignorePaths,
onlyAllowPaths
);
for (const key of allReallySkipKeys) {
delete finalMappings[key];
}
profiler?.insert("ensembleMixedEnties: finish parsing all skip items");
profiler?.insertSize("sizeof finalMappings", finalMappings);
// console.debug("in the end of ensembleMixedEnties, finalMappings is:"); // console.debug("in the end of ensembleMixedEnties, finalMappings is:");
// console.debug(finalMappings); // console.debug(finalMappings);
@@ -383,7 +532,7 @@ const getSyncPlanInplace = async (
) => { ) => {
profiler?.addIndent(); profiler?.addIndent();
profiler?.insert("getSyncPlanInplace: enter"); profiler?.insert("getSyncPlanInplace: enter");
// from long(deep) to short(shadow) // from long(deep) to short(shadow), descending
const sortedKeys = Object.keys(mixedEntityMappings).sort( const sortedKeys = Object.keys(mixedEntityMappings).sort(
(k1, k2) => k2.length - k1.length (k1, k2) => k2.length - k1.length
); );
+154
View File
@@ -0,0 +1,154 @@
import { strict as assert } from "assert";
import { checkIsSkipItemOrNotByName } from "../src/sync";
describe("Sync: checkIsSkipItemOrNotByName", () => {
it("should be ok everywhere for empty config", async () => {
let isSkip = checkIsSkipItemOrNotByName(
"xxx.md",
false,
false,
false,
".obsidian",
/* ignorePaths */ [],
/* onlyAllowPaths */ []
).finalIsIgnored;
assert.ok(!isSkip);
isSkip = checkIsSkipItemOrNotByName(
"xxx.md",
false,
false,
false,
".obsidian",
/* ignorePaths */ [""],
/* onlyAllowPaths */ ["", "\n"]
).finalIsIgnored;
assert.ok(!isSkip);
});
it("should be ok for deny list", async () => {
let isSkip = checkIsSkipItemOrNotByName(
"xxx.md",
false,
false,
false,
".obsidian",
/* ignorePaths */ ["xxx"],
/* onlyAllowPaths */ []
).finalIsIgnored;
assert.ok(isSkip);
isSkip = checkIsSkipItemOrNotByName(
"yyy.md",
false,
false,
false,
".obsidian",
/* ignorePaths */ ["xxx"],
/* onlyAllowPaths */ []
).finalIsIgnored;
assert.ok(!isSkip);
isSkip = checkIsSkipItemOrNotByName(
"xxx.md",
false,
false,
false,
".obsidian",
/* ignorePaths */ ["xxx$"],
/* onlyAllowPaths */ []
).finalIsIgnored;
assert.ok(!isSkip);
// if we deny a folder, we have to deny all the sub files
// TODO: it's soooo hard to do the path resolution in this func with regex,
// so we defer the detection to later steps now.
// the test here doesn't work.
// isSkip = checkIsSkipItemOrNotByName(
// 'xxx/yyy.md',
// false,
// false,
// false,
// '.obsidian',
// /* ignorePaths */ ['xxx/$'],
// /* onlyAllowPaths */ []
// ).finalIsIgnored;
// assert.ok(isSkip);
});
it("should be ok for allow list", async () => {
let isSkip = checkIsSkipItemOrNotByName(
"xxx.md",
false,
false,
false,
".obsidian",
/* ignorePaths */ [],
/* onlyAllowPaths */ ["xxx"]
).finalIsIgnored;
assert.ok(!isSkip);
isSkip = checkIsSkipItemOrNotByName(
"yyy.md",
false,
false,
false,
".obsidian",
/* ignorePaths */ [""],
/* onlyAllowPaths */ ["xxx"]
).finalIsIgnored;
assert.ok(isSkip);
isSkip = checkIsSkipItemOrNotByName(
"xxx.md",
false,
false,
false,
".obsidian",
/* ignorePaths */ [],
/* onlyAllowPaths */ ["xxx$"]
).finalIsIgnored;
assert.ok(isSkip);
// should NOT skip because we allow the sub file AND not deny the folder
// TODO: it's soooo hard to do the path resolution in this func with regex,
// so we defer the detection to later steps now.
// the test here doesn't work.
// isSkip = checkIsSkipItemOrNotByName(
// 'xxx/',
// false,
// false,
// false,
// '.obsidian',
// /* ignorePaths */ [],
// /* onlyAllowPaths */ ['xxx/yyy.md']
// ).finalIsIgnored;
// assert.ok(!isSkip);
});
it("should detect the name by two lists together", async () => {
// should skip because we ignore the path
let isSkip = checkIsSkipItemOrNotByName(
"xxx.md",
false,
false,
false,
".obsidian",
/* ignorePaths */ ["xxx"],
/* onlyAllowPaths */ ["yyy"]
).finalIsIgnored;
assert.ok(isSkip);
// should skip because we disallow the whole folder
isSkip = checkIsSkipItemOrNotByName(
"xxx/yyy.md",
false,
false,
false,
".obsidian",
/* ignorePaths */ ["xxx"],
/* onlyAllowPaths */ ["xxx/yyy.md"]
).finalIsIgnored;
assert.ok(isSkip);
});
});
+1
View File
@@ -140,6 +140,7 @@ export type CipherMethodType = "rclone-base64" | "openssl-base64" | "unknown";
export type QRExportType = "basic_and_advanced" | SUPPORTED_SERVICES_TYPE; export type QRExportType = "basic_and_advanced" | SUPPORTED_SERVICES_TYPE;
export interface ProfilerConfig { export interface ProfilerConfig {
enable?: boolean;
enablePrinting?: boolean; enablePrinting?: boolean;
recordSize?: boolean; recordSize?: boolean;
} }
+3 -1
View File
@@ -340,7 +340,7 @@
"settings_obfuscatesettingfile": "Obfuscate The Setting File Or Not", "settings_obfuscatesettingfile": "Obfuscate The Setting File Or Not",
"settings_obfuscatesettingfile_desc": "The setting file (data.json) has some sensitive information. It's strongly recommended to obfuscate it to avoid unexpected read and modification. If you are sure to view and edit it manually, you can disable the obfuscation.", "settings_obfuscatesettingfile_desc": "The setting file (data.json) has some sensitive information. It's strongly recommended to obfuscate it to avoid unexpected read and modification. If you are sure to view and edit it manually, you can disable the obfuscation.",
"settings_viewconsolelog": "View Console Log", "settings_viewconsolelog": "View Console Log",
"settings_viewconsolelog_desc": "On desktop, please press \"ctrl+shift+i\" or \"cmd+shift+i\" to view the log. On mobile, please install the third-party plugin <a href='https://obsidian.md/plugins?search=Logstravaganza'>Logstravaganza</a> to export the console log to a note.", "settings_viewconsolelog_desc": "On desktop, please press \"ctrl+shift+i\" or \"cmd+option+i\" to view the log. On mobile, please install the third-party plugin <a href='https://obsidian.md/plugins?search=Logstravaganza'>Logstravaganza</a> to export the console log to a note.",
"settings_syncplans": "Export Sync Plans", "settings_syncplans": "Export Sync Plans",
"settings_syncplans_desc": "Sync plans are created every time after you trigger sync and before the actual sync. Useful to know what would actually happen in those sync. Click the button to export sync plans.", "settings_syncplans_desc": "Sync plans are created every time after you trigger sync and before the actual sync. Useful to know what would actually happen in those sync. Click the button to export sync plans.",
"settings_syncplans_button_1_only_change": "Export latest 1 (change part)", "settings_syncplans_button_1_only_change": "Export latest 1 (change part)",
@@ -361,6 +361,8 @@
"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_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_notice": "Profiler results exported.",
"settings_profiler_results_button_all": "Export All", "settings_profiler_results_button_all": "Export All",
"settings_profiler_enableprofiler": "Enable Profiler",
"settings_profiler_enableprofiler_desc": "Collect performance data or not?",
"settings_profiler_enabledebugprint": "Enable Profiler Printing", "settings_profiler_enabledebugprint": "Enable Profiler Printing",
"settings_profiler_enabledebugprint_desc": "Print profiler result in each insertion to console or not?", "settings_profiler_enabledebugprint_desc": "Print profiler result in each insertion to console or not?",
"settings_profiler_recordsize": "Enable Profiler Recording Size", "settings_profiler_recordsize": "Enable Profiler Recording Size",
+6
View File
@@ -360,6 +360,12 @@
"settings_profiler_results_desc": "插件记录了每次同步每一步的耗时。这里可以导出记录得知哪一步最慢。", "settings_profiler_results_desc": "插件记录了每次同步每一步的耗时。这里可以导出记录得知哪一步最慢。",
"settings_profiler_results_notice": "性能数据已导出", "settings_profiler_results_notice": "性能数据已导出",
"settings_profiler_results_button_all": "导出所有", "settings_profiler_results_button_all": "导出所有",
"settings_profiler_enableprofiler": "性能收集",
"settings_profiler_enableprofiler_desc": "是否开启性能收集功能?",
"settings_profiler_enabledebugprint": "性能收集输出",
"settings_profiler_enabledebugprint_desc": "是否直接输出性能收集结果到终端里?",
"settings_profiler_recordsize": "性能收集统计对象大小",
"settings_profiler_recordsize_desc": "是否收集对象的大小?",
"settings_outputbasepathvaultid": "输出资料库对应的位置和随机分配的 ID", "settings_outputbasepathvaultid": "输出资料库对应的位置和随机分配的 ID",
"settings_outputbasepathvaultid_desc": "用于调试。", "settings_outputbasepathvaultid_desc": "用于调试。",
"settings_outputbasepathvaultid_button": "输出", "settings_outputbasepathvaultid_button": "输出",
+6
View File
@@ -359,6 +359,12 @@
"settings_profiler_results_desc": "外掛記錄了每次同步每一步的耗時。這裡可以匯出記錄得知哪一步最慢。", "settings_profiler_results_desc": "外掛記錄了每次同步每一步的耗時。這裡可以匯出記錄得知哪一步最慢。",
"settings_profiler_results_notice": "效能資料已匯出", "settings_profiler_results_notice": "效能資料已匯出",
"settings_profiler_results_button_all": "匯出所有", "settings_profiler_results_button_all": "匯出所有",
"settings_profiler_enableprofiler": "效能收集",
"settings_profiler_enableprofiler_desc": "是否開啟效能收集功能?",
"settings_profiler_enabledebugprint": "效能收集輸出",
"settings_profiler_enabledebugprint_desc": "是否直接輸出效能收集結果到終端裡?",
"settings_profiler_recordsize": "效能收集統計物件大小",
"settings_profiler_recordsize_desc": "是否收集物件的大小?",
"settings_outputbasepathvaultid": "輸出資料庫對應的位置和隨機分配的 ID", "settings_outputbasepathvaultid": "輸出資料庫對應的位置和隨機分配的 ID",
"settings_outputbasepathvaultid_desc": "用於除錯。", "settings_outputbasepathvaultid_desc": "用於除錯。",
"settings_outputbasepathvaultid_button": "輸出", "settings_outputbasepathvaultid_button": "輸出",
+12 -7
View File
@@ -108,7 +108,7 @@ import {
upsertPluginVersionByVault, upsertPluginVersionByVault,
} from "./localdb"; } from "./localdb";
import { changeMobileStatusBar } from "./misc"; import { changeMobileStatusBar } from "./misc";
import { DEFAULT_PROFILER_CONFIG, type Profiler } from "./profiler"; import { DEFAULT_PROFILER_CONFIG, Profiler } from "./profiler";
import { RemotelySaveSettingTab } from "./settings"; import { RemotelySaveSettingTab } from "./settings";
import { SyncAlgoV3Modal } from "./syncAlgoV3Notice"; import { SyncAlgoV3Modal } from "./syncAlgoV3Notice";
@@ -232,12 +232,14 @@ export default class RemotelySavePlugin extends Plugin {
appContainerObserver?: MutationObserver; appContainerObserver?: MutationObserver;
async syncRun(triggerSource: SyncTriggerSourceType = "manual") { async syncRun(triggerSource: SyncTriggerSourceType = "manual") {
// const profiler = new Profiler( let profiler: Profiler | undefined = undefined;
// undefined, if (this.settings.profiler?.enable ?? false) {
// this.settings.profiler?.enablePrinting ?? false, profiler = new Profiler(
// this.settings.profiler?.recordSize ?? false undefined,
// ); this.settings.profiler?.enablePrinting ?? false,
const profiler: Profiler | undefined = undefined; this.settings.profiler?.recordSize ?? false
);
}
const fsLocal = new FakeFsLocal( const fsLocal = new FakeFsLocal(
this.app.vault, this.app.vault,
this.settings.syncConfigDir ?? false, this.settings.syncConfigDir ?? false,
@@ -1471,6 +1473,9 @@ export default class RemotelySavePlugin extends Plugin {
if (this.settings.profiler === undefined) { if (this.settings.profiler === undefined) {
this.settings.profiler = DEFAULT_PROFILER_CONFIG; this.settings.profiler = DEFAULT_PROFILER_CONFIG;
} }
if (this.settings.profiler.enable === undefined) {
this.settings.profiler.enable = false;
}
if (this.settings.profiler.enablePrinting === undefined) { if (this.settings.profiler.enablePrinting === undefined) {
this.settings.profiler.enablePrinting = false; this.settings.profiler.enablePrinting = false;
} }
+1
View File
@@ -10,6 +10,7 @@ interface BreakPoint {
} }
export const DEFAULT_PROFILER_CONFIG: ProfilerConfig = { export const DEFAULT_PROFILER_CONFIG: ProfilerConfig = {
enable: false,
enablePrinting: false, enablePrinting: false,
recordSize: false, recordSize: false,
}; };
+19
View File
@@ -2940,6 +2940,25 @@ export class RemotelySaveSettingTab extends PluginSettingTab {
}); });
}); });
new Setting(debugDiv)
.setName(t("settings_profiler_enableprofiler"))
.setDesc(t("settings_profiler_enableprofiler_desc"))
.addDropdown((dropdown) => {
dropdown.addOption("enable", t("enable"));
dropdown.addOption("disable", t("disable"));
dropdown
.setValue(
this.plugin.settings.profiler?.enable ? "enable" : "disable"
)
.onChange(async (val: string) => {
if (this.plugin.settings.profiler === undefined) {
this.plugin.settings.profiler = DEFAULT_PROFILER_CONFIG;
}
this.plugin.settings.profiler.enable = val === "enable";
await this.plugin.saveSettings();
});
});
new Setting(debugDiv) new Setting(debugDiv)
.setName(t("settings_profiler_enabledebugprint")) .setName(t("settings_profiler_enabledebugprint"))
.setDesc(t("settings_profiler_enabledebugprint_desc")) .setDesc(t("settings_profiler_enabledebugprint_desc"))