Compare commits

..
3 Commits
Author SHA1 Message Date
fyears 272bc7a543 bump to 0.5.7
Release A New Version / build (16.x) (push) Failing after 52s
2024-06-17 00:15:59 +08:00
fyears 35f5355c8e optimize sync status bar 2024-06-17 00:15:33 +08:00
fyears 57493f57cf polish doc 2024-06-16 20:07:49 +08:00
13 changed files with 219 additions and 82 deletions
+17 -4
View File
@@ -22,7 +22,7 @@ This is yet another unofficial sync plugin for Obsidian. If you like it or find
- OneDrive for personal
- Webdav (NextCloud / InfiniCloud / Synology webdav server / ...)
- Webdis
- Google Drive (PRO feature)
- Google Drive (GDrive) (PRO feature)
- Box (PRO feature)
- pCloud (PRO feature)
- Yandex Disk (PRO feature)
@@ -34,7 +34,7 @@ This is yet another unofficial sync plugin for Obsidian. If you like it or find
- **[Minimal Intrusive](./docs/minimal_intrusive_design.md).**
- **Skip Large files** and **skip paths** by custom regex conditions!
- **[Sync Algorithm](./docs/sync_algorithm/v3/intro.md) is provided for discussion.**
- **[Basic Conflict Detection And Handling](./docs/sync_algorithm/v3/intro.md)** for free version. **[Advanced Conflict Handling](./pro/README.md)** for PRO version.
- **[Basic Conflict Detection And Handling](./docs/sync_algorithm/v3/intro.md)** for free version. **[Advanced Smart Conflict Handling](./pro/README.md)** for PRO version.
- Source Available. See [License](./LICENSE) for details.
## Limitations
@@ -127,7 +127,7 @@ Additionally, the plugin author may occasionally visit Obsidian official forum a
- Mostly experimental.
- You have to setup and protect your web server by yourself.
### Google Drive (PRO feature)
### Google Drive (GDrive) (PRO feature)
PRO (paid) feature "sync with Google Drive" allows users to to sync with Google Drive. Tutorials and limitations are documented [here](./docs/remote_services/googledrive/README.md).
@@ -143,16 +143,29 @@ PRO (paid) feature "sync with pCloud" allows users to to sync with pCloud (using
PRO (paid) feature "sync with Yandex Disk" allows users to to sync with Yandex Disk (using its native API instead of webdav). Tutorials and limitations are documented [here](./docs/remote_services/yandexdisk/README.md).
### Yandex Disk (PRO feature)
### Koofr (PRO feature)
PRO (paid) feature "sync with Koofr" allows users to to sync with Koofr (using its native API instead of webdav). Tutorials and limitations are documented [here](./docs/remote_services/koofr/README.md).
## Smart Conflict (PRO feature)
Basic (free) version can detect conflicts, but users have to choose to keep newer version or larger version of the files.
PRO (paid) feature "Smart Conflict" gives users one more option: merge small markdown files, or duplicate large markdown files or non-markdown files.
See documents [here](./docs/pro/README.md)
## Scheduled Auto Sync
- You can configure auto syncing every N minutes in settings.
- In auto sync mode, if any error occurs, the plugin would **fail silently**.
- Auto sync only works when Obsidian is being opened. It's **technically impossible** to auto sync while Obsidian is in background, because the plugin just works in the browser environment provided by Obsidian.
## Sync On Save
- You can configure sync on save in settings.
- In sync on save mode, if any error occurs, the plugin would **fail silently**.
## How To Deal With Hidden Files Or Folders
**By default, all files or folder starting with `.` (dot) or `_` (underscore) are treated as hidden files, and would NOT be synced.** It's useful if you have some files just staying locally. But this strategy also means that themes / other plugins / settings of this plugin would neither be synced.
+1 -1
View File
@@ -1,4 +1,4 @@
# Google Drive (PRO)
# Google Drive (GDrive) (PRO)
# Intro
+1 -1
View File
@@ -1,7 +1,7 @@
{
"id": "remotely-save",
"name": "Remotely Save",
"version": "0.5.6",
"version": "0.5.7",
"minAppVersion": "0.13.21",
"description": "Yet another unofficial plugin allowing users to synchronize notes between local device and the cloud service.",
"author": "fyears",
+1 -1
View File
@@ -1,7 +1,7 @@
{
"id": "remotely-save",
"name": "Remotely Save",
"version": "0.5.6",
"version": "0.5.7",
"minAppVersion": "0.13.21",
"description": "Yet another unofficial plugin allowing users to synchronize notes between local device and the cloud service.",
"author": "fyears",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "remotely-save",
"version": "0.5.6",
"version": "0.5.7",
"description": "This is yet another sync plugin for Obsidian app.",
"scripts": {
"dev2": "node esbuild.config.mjs --watch",
+19 -3
View File
@@ -6,18 +6,34 @@ Remotely Save has some "pro features", which users have to pay for using them.
## Sign Up / Sign In And Connect
See the tutorial about your PRO account [here](../docs/pro/README.md).
See the tutorial about your PRO account [here](.../docs/pro/README.md).
## Smart Conflict
## Smart Conflict (PRO feature)
Basic (free) version can detect conflicts, but users have to choose to keep newer version or larger version of the files.
PRO (paid) feature "Smart Conflict" gives users one more option: merge small markdown files, or duplicate large markdown files or non-markdown files.
## Sync With Google Drive
## Google Drive (GDrive) (PRO feature)
PRO (paid) feature "sync with Google Drive" allows users to to sync with Google Drive. Tutorials and limitations are documented [here](../docs/remote_services/googledrive/README.md).
## Box (PRO feature)
PRO (paid) feature "sync with Box" allows users to to sync with Box. Tutorials and limitations are documented [here](../docs/remote_services/box/README.md).
## pCloud (PRO feature)
PRO (paid) feature "sync with pCloud" allows users to to sync with pCloud (using its native API instead of webdav). Tutorials and limitations are documented [here](../docs/remote_services/pcloud/README.md).
## Yandex Disk (PRO feature)
PRO (paid) feature "sync with Yandex Disk" allows users to to sync with Yandex Disk (using its native API instead of webdav). Tutorials and limitations are documented [here](../docs/remote_services/yandexdisk/README.md).
## Koofr (PRO feature)
PRO (paid) feature "sync with Koofr" allows users to to sync with Koofr (using its native API instead of webdav). Tutorials and limitations are documented [here](../docs/remote_services/koofr/README.md).
## License
The codes or files or subfolders inside the current folder (`pro` in the repo), are released under "source available" license: "PolyForm Strict License 1.0.0".
+6 -1
View File
@@ -1417,6 +1417,7 @@ export const doActualSync = async (
db: InternalDBs,
profiler: Profiler | undefined,
conflictAction: ConflictActionType,
triggerSource: SyncTriggerSourceType,
callbackSyncProcess?: any
) => {
profiler?.addIndent();
@@ -1524,13 +1525,16 @@ export const doActualSync = async (
// );
await callbackSyncProcess?.(
triggerSource,
realCounter,
realTotalCount,
key,
val.decision
);
realCounter += 1;
if (val.change === undefined || val.change) {
realCounter += 1;
}
await dispatchOperationToActualV3(
key,
@@ -1743,6 +1747,7 @@ export async function syncer(
db,
profiler,
settings.conflictAction ?? "keep_newer",
triggerSource,
callbackSyncProcess
);
profiler?.insert(`finish step${step} (actual sync)`);
+17 -11
View File
@@ -45,20 +45,26 @@
"command_exportlogsindb": "export logs saved in db",
"statusbar_time_years": "Synced {{time}} years ago",
"statusbar_time_months": "Synced {{time}} months ago",
"statusbar_time_weeks": "Synced {{time}} weeks ago",
"statusbar_time_days": "Synced {{time}} days ago",
"statusbar_time_hours": "Synced {{time}} hours ago",
"statusbar_time_minutes": "Synced {{time}} minutes ago",
"statusbar_time_lessminute": "Synced last minute ago",
"statusbar_lastsync": "Synced {{time}} ago",
"statusbar_sync_source_manual": "Manual: ",
"statusbar_sync_source_dry": "Dry: ",
"statusbar_sync_source_auto": "Auto: ",
"statusbar_sync_source_auto_once_init": "Auto (init): ",
"statusbar_sync_source_auto_sync_on_save": "Auto (save): ",
"statusbar_sync_status_prefix_success": "Successfully synced ",
"statusbar_sync_status_prefix_failed": "Failed to sync ",
"statusbar_time_years": "{{time}} years ago",
"statusbar_time_months": "{{time}} months ago",
"statusbar_time_weeks": "{{time}} weeks ago",
"statusbar_time_days": "{{time}} days ago",
"statusbar_time_hours": "{{time}} hours ago",
"statusbar_time_minutes": "{{time}} minutes ago",
"statusbar_time_lessminute": "last minute ago",
"statusbar_time_now": "just now",
"statusbar_syncing": "Syncing...",
"statusbar_failed": "Last sync failed",
"statusbar_now": "Synced just now",
"statusbar_lastsync_label": "Last successful Sync on {{date}}",
"statusbar_lastsync_label": "on {{date}}",
"statusbar_lastsync_never": "Never Synced",
"statusbar_lastsync_never_label": "Never Synced before",
"modal_password_title": "Hold on and PLEASE READ ON...",
"modal_password_shortdesc": "If the field is not empty, files would be encrypted locally before being uploaded.\nIf the field is empty, then files would be uploaded without encryption.",
"modal_password_attn1": "Attention 1/5: The vault name is NOT encrypted. The plugin creates a folder with the vault name on some remote services.",
+17 -11
View File
@@ -45,20 +45,26 @@
"command_exportsyncplans_all": "导出同步计划(所有)(export sync plans (all)",
"command_exportlogsindb": "从数据库导出终端日志(export logs saved in db",
"statusbar_time_years": "{{time}} 年前同步",
"statusbar_time_months": "{{time}} 月前同步",
"statusbar_time_weeks": "{{time}} 周前同步",
"statusbar_time_days": "{{time}} 天前同步",
"statusbar_time_hours": "{{time}} 小时前同步",
"statusbar_time_minutes": "{{time}} 分钟前同步",
"statusbar_time_lessminute": "一分钟之内同步",
"statusbar_lastsync": "上一次同步于:{{time}}",
"statusbar_sync_source_manual": "手动:",
"statusbar_sync_source_dry": "空跑:",
"statusbar_sync_source_auto": "自动:",
"statusbar_sync_source_auto_once_init": "启动时自动:",
"statusbar_sync_source_auto_sync_on_save": "保存时自动:",
"statusbar_sync_status_prefix_success": "同步成功:",
"statusbar_sync_status_prefix_failed": "同步失败:",
"statusbar_time_years": "{{time}} 年前",
"statusbar_time_months": "{{time}} 月前",
"statusbar_time_weeks": "{{time}} 周前",
"statusbar_time_days": "{{time}} 天前",
"statusbar_time_hours": "{{time}} 小时前",
"statusbar_time_minutes": "{{time}} 分钟前",
"statusbar_time_lessminute": "一分钟之内",
"statusbar_time_now": "刚刚",
"statusbar_syncing": "正在同步",
"statusbar_failed": "上次同步失败了",
"statusbar_now": "刚同步完",
"statusbar_lastsync_label": "上一次同步于:{{date}}",
"statusbar_lastsync_label": "日期:{{date}}",
"statusbar_lastsync_never": "没触发过同步",
"statusbar_lastsync_never_label": "没触发过同步",
"modal_password_title": "稍等一下,请阅读下文:",
"modal_password_shortdesc": "如果密码不是空的,那么文件会在上传之前,在本地先用此密码加密。\n如果密码是空的,那么文件会被非加密地上传。",
"modal_password_attn1": "注意 1/5:库(Vault)名字是不会加密的!本插件会在一些远程存储里创建一个和库名字有着同名的文件夹。",
+17 -11
View File
@@ -44,20 +44,26 @@
"command_exportsyncplans_all": "匯出同步計劃(所有)(export sync plans (all)",
"command_exportlogsindb": "從資料庫匯出終端日誌(export logs saved in db",
"statusbar_time_years": "{{time}} 年前同步",
"statusbar_time_months": "{{time}} 月前同步",
"statusbar_time_weeks": "{{time}} 周前同步",
"statusbar_time_days": "{{time}} 天前同步",
"statusbar_time_hours": "{{time}} 小時前同步",
"statusbar_time_minutes": "{{time}} 分鐘前同步",
"statusbar_time_lessminute": "一分鐘之內同步",
"statusbar_lastsync": "上一次同步於:{{time}}",
"statusbar_sync_source_manual": "手動:",
"statusbar_sync_source_dry": "空跑:",
"statusbar_sync_source_auto": "自動:",
"statusbar_sync_source_auto_once_init": "啟動時自動:",
"statusbar_sync_source_auto_sync_on_save": "儲存時自動:",
"statusbar_sync_status_prefix_success": "同步成功:",
"statusbar_sync_status_prefix_failed": "同步失敗:",
"statusbar_time_years": "{{time}} 年前",
"statusbar_time_months": "{{time}} 月前",
"statusbar_time_weeks": "{{time}} 周前",
"statusbar_time_days": "{{time}} 天前",
"statusbar_time_hours": "{{time}} 小時前",
"statusbar_time_minutes": "{{time}} 分鐘前",
"statusbar_time_lessminute": "一分鐘之內",
"statusbar_time_now": "剛剛",
"statusbar_syncing": "正在同步",
"statusbar_failed": "上次同步失敗了",
"statusbar_now": "剛同步完",
"statusbar_lastsync_label": "上一次同步於:{{date}}",
"statusbar_lastsync_label": "日期:{{date}}",
"statusbar_lastsync_never": "沒觸發過同步",
"statusbar_lastsync_never_label": "沒觸發過同步",
"modal_password_title": "稍等一下,請閱讀下文:",
"modal_password_shortdesc": "如果密碼不是空的,那麼檔案會在上傳之前,在本地先用此密碼加密。\n如果密碼是空的,那麼檔案會被非加密地上傳。",
"modal_password_attn1": "注意 1/5:儲存庫(Vault)名字是不會加密的!本外掛會在一些遠端儲存裡建立一個和庫名字有著同名的資料夾。",
+21 -1
View File
@@ -519,7 +519,27 @@ export const getLastSuccessSyncTimeByVault = async (
) => {
return (await db.simpleKVForMiscTbl.getItem(
`${vaultRandomID}-lastSuccessSyncMillis`
)) as number;
)) as number | null | undefined;
};
export const upsertLastFailedSyncTimeByVault = async (
db: InternalDBs,
vaultRandomID: string,
millis: number
) => {
await db.simpleKVForMiscTbl.setItem(
`${vaultRandomID}-lastFailedSyncMillis`,
millis
);
};
export const getLastFailedSyncTimeByVault = async (
db: InternalDBs,
vaultRandomID: string
) => {
return (await db.simpleKVForMiscTbl.getItem(
`${vaultRandomID}-lastFailedSyncMillis`
)) as number | null | undefined;
};
export const upsertPluginVersionByVault = async (
+94 -35
View File
@@ -92,8 +92,10 @@ import {
type InternalDBs,
clearAllLoggerOutputRecords,
clearExpiredSyncPlanRecords,
getLastFailedSyncTimeByVault,
getLastSuccessSyncTimeByVault,
prepareDBs,
upsertLastFailedSyncTimeByVault,
upsertLastSuccessSyncTimeByVault,
upsertPluginVersionByVault,
} from "./localdb";
@@ -176,6 +178,29 @@ const getIconSvg = () => {
return res;
};
const getStatusBarShortMsgFromSyncSource = (
t: (x: TransItemType, vars?: any) => string,
s: SyncTriggerSourceType | undefined
) => {
if (s === undefined) {
return "";
}
switch (s) {
case "manual":
return t("statusbar_sync_source_manual");
case "dry":
return t("statusbar_sync_source_dry");
case "auto":
return t("statusbar_sync_source_auto");
case "auto_once_init":
return t("statusbar_sync_source_auto_once_init");
case "auto_sync_on_save":
return t("statusbar_sync_source_auto_sync_on_save");
default:
throw Error(`no translate for ${s}`);
}
};
export default class RemotelySavePlugin extends Plugin {
settings!: RemotelySavePluginSettings;
db!: InternalDBs;
@@ -393,17 +418,15 @@ export default class RemotelySavePlugin extends Plugin {
) => {
if (step === 1) {
// change status to "syncing..." on statusbar
this.updateLastSuccessSyncMsg(-1);
this.updateLastSyncMsg(s, -1, -1);
} else if (step === 8 && everythingOk) {
const lastSuccessSyncMillis = Date.now();
await upsertLastSuccessSyncTimeByVault(
this.db,
this.vaultRandomID,
lastSuccessSyncMillis
);
this.updateLastSuccessSyncMsg(lastSuccessSyncMillis);
const ts = Date.now();
await upsertLastSuccessSyncTimeByVault(this.db, this.vaultRandomID, ts);
this.updateLastSyncMsg(s, ts, null);
} else if (!everythingOk) {
this.updateLastSuccessSyncMsg(-2); // magic number
const ts = Date.now();
await upsertLastFailedSyncTimeByVault(this.db, this.vaultRandomID, ts);
this.updateLastSyncMsg(s, null, ts); // magic number
}
};
@@ -412,12 +435,15 @@ export default class RemotelySavePlugin extends Plugin {
};
const callbackSyncProcess = async (
s: SyncTriggerSourceType,
realCounter: number,
realTotalCount: number,
pathName: string,
decision: string
) => {
this.setCurrSyncMsg(
t,
s,
realCounter,
realTotalCount,
pathName,
@@ -1060,15 +1086,21 @@ export default class RemotelySavePlugin extends Plugin {
this.statusBarElement = statusBarItem.createEl("span");
this.statusBarElement.setAttribute("data-tooltip-position", "top");
this.updateLastSuccessSyncMsg(
await getLastSuccessSyncTimeByVault(this.db, this.vaultRandomID)
this.updateLastSyncMsg(
undefined,
await getLastSuccessSyncTimeByVault(this.db, this.vaultRandomID),
await getLastFailedSyncTimeByVault(this.db, this.vaultRandomID)
);
// update statusbar text every 30 seconds
this.registerInterval(
window.setInterval(async () => {
this.updateLastSuccessSyncMsg(
await getLastSuccessSyncTimeByVault(this.db, this.vaultRandomID)
);
if (!this.isSyncing) {
this.updateLastSyncMsg(
undefined,
await getLastSuccessSyncTimeByVault(this.db, this.vaultRandomID),
await getLastFailedSyncTimeByVault(this.db, this.vaultRandomID)
);
}
}, 1000 * 30)
);
}
@@ -1618,7 +1650,8 @@ export default class RemotelySavePlugin extends Plugin {
if (
caller === "SYNC" ||
(caller === "FILE_CHANGES" && lastModified > lastSuccessSyncMillis)
(caller === "FILE_CHANGES" &&
lastModified > (lastSuccessSyncMillis ?? 1))
) {
console.debug(
`so lastModified > lastSuccessSyncMillis or it's called while syncing before`
@@ -1734,17 +1767,34 @@ export default class RemotelySavePlugin extends Plugin {
}
setCurrSyncMsg(
t: (x: TransItemType, vars?: any) => string,
s: SyncTriggerSourceType,
i: number,
totalCount: number,
pathName: string,
decision: string,
triggerSource: SyncTriggerSourceType
) {
const msg = `syncing progress=${i}/${totalCount},decision=${decision},path=${pathName},source=${triggerSource}`;
this.currSyncMsg = msg;
const L = `${totalCount}`.length;
const iStr = `${i}`.padStart(L, "0");
const prefix = getStatusBarShortMsgFromSyncSource(t, s);
const shortMsg = prefix + `Syncing ${iStr}/${totalCount}`;
const longMsg =
prefix +
`Syncing progress=${iStr}/${totalCount},decision=${decision},path=${pathName},source=${triggerSource}`;
this.currSyncMsg = longMsg;
if (this.statusBarElement !== undefined) {
this.statusBarElement.setText(shortMsg);
this.statusBarElement.setAttribute("aria-label", longMsg);
}
}
updateLastSuccessSyncMsg(lastSuccessSyncMillis?: number) {
updateLastSyncMsg(
s: SyncTriggerSourceType | undefined,
lastSuccessSyncMillis: number | null | undefined,
lastFailedSyncMillis: number | null | undefined
) {
if (this.statusBarElement === undefined) return;
const t = (x: TransItemType, vars?: any) => {
@@ -1754,18 +1804,27 @@ export default class RemotelySavePlugin extends Plugin {
let lastSyncMsg = t("statusbar_lastsync_never");
let lastSyncLabelMsg = t("statusbar_lastsync_never_label");
if (lastSuccessSyncMillis !== undefined && lastSuccessSyncMillis === -1) {
lastSyncMsg = t("statusbar_syncing");
}
const inputTs = Math.max(
lastSuccessSyncMillis ?? -999,
lastFailedSyncMillis ?? -999
);
const isSuccess =
(lastSuccessSyncMillis ?? -999) >= (lastFailedSyncMillis ?? -999);
if (lastSuccessSyncMillis !== undefined && lastSuccessSyncMillis === -2) {
lastSyncMsg = t("statusbar_failed");
lastSyncLabelMsg = t("statusbar_failed");
}
if (lastSuccessSyncMillis !== undefined && lastSuccessSyncMillis > 0) {
const deltaTime = Date.now() - lastSuccessSyncMillis;
if (lastSuccessSyncMillis === -1) {
// magic number
// otherwise how can we know we are syncing??
lastSyncMsg =
getStatusBarShortMsgFromSyncSource(t, s!) + t("statusbar_syncing");
} else if (inputTs > 0) {
let prefix = "";
if (isSuccess) {
prefix = t("statusbar_sync_status_prefix_success");
} else {
prefix = t("statusbar_sync_status_prefix_failed");
}
const deltaTime = Date.now() - inputTs;
// create human readable time
const years = Math.floor(deltaTime / 31556952000);
const months = Math.floor(deltaTime / 2629746000);
@@ -1774,9 +1833,7 @@ export default class RemotelySavePlugin extends Plugin {
const hours = Math.floor(deltaTime / 3600000);
const minutes = Math.floor(deltaTime / 60000);
const seconds = Math.floor(deltaTime / 1000);
let timeText = "";
if (years > 0) {
timeText = t("statusbar_time_years", { time: years });
} else if (months > 0) {
@@ -1792,10 +1849,9 @@ export default class RemotelySavePlugin extends Plugin {
} else if (seconds > 30) {
timeText = t("statusbar_time_lessminute");
} else {
timeText = t("statusbar_now");
timeText = t("statusbar_time_now");
}
const dateText = new Date(lastSuccessSyncMillis).toLocaleTimeString(
const dateText = new Date(inputTs).toLocaleTimeString(
navigator.language,
{
weekday: "long",
@@ -1805,8 +1861,11 @@ export default class RemotelySavePlugin extends Plugin {
}
);
lastSyncMsg = timeText;
lastSyncLabelMsg = t("statusbar_lastsync_label", { date: dateText });
lastSyncMsg = prefix + timeText;
lastSyncLabelMsg =
prefix + t("statusbar_lastsync_label", { date: dateText });
} else {
// TODO: no idea what happened.
}
this.statusBarElement.setText(lastSyncMsg);
+7 -1
View File
@@ -55,6 +55,7 @@ import {
clearAllPrevSyncRecordByVault,
clearAllSyncPlanRecords,
destroyDBs,
upsertLastFailedSyncTimeByVault,
upsertLastSuccessSyncTimeByVault,
} from "./localdb";
import type RemotelySavePlugin from "./main"; // unavoidable
@@ -2134,7 +2135,12 @@ export class RemotelySaveSettingTab extends PluginSettingTab {
this.plugin.vaultRandomID,
-1
);
this.plugin.updateLastSuccessSyncMsg(-1);
await upsertLastFailedSyncTimeByVault(
this.plugin.db,
this.plugin.vaultRandomID,
-1
);
this.plugin.updateLastSyncMsg(undefined, null, null);
new Notice(t("settings_resetstatusbar_notice"));
});
});