Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
92183e0fb6 | ||
|
|
28bd861c9e | ||
|
|
4e8bec9511 | ||
|
|
c98d91ab60 | ||
|
|
8bd456b08e | ||
|
|
a5c25aecd0 | ||
|
|
806438964c | ||
|
|
069e993fb0 | ||
|
|
7c2a17c404 | ||
|
|
f26e491922 | ||
|
|
dbf80c3359 | ||
|
|
9ddb840077 | ||
|
|
17135abbd4 | ||
|
|
af8357ba6e | ||
|
|
bfa23eb8a5 | ||
|
|
d3d26bfe49 | ||
|
|
f6f7d83a35 | ||
|
|
9e29405103 | ||
|
|
3e7e05fc3d | ||
|
|
391df3ed2d | ||
|
|
c542789c93 |
@@ -0,0 +1,17 @@
|
|||||||
|
# Sync Ignoring Large Files
|
||||||
|
|
||||||
|
Initially, the plugin does not ignore large files.
|
||||||
|
|
||||||
|
From the new version in May 2022, it can ignore all files with some sizes. But we need some rules to make the function compatible with existing conditions.
|
||||||
|
|
||||||
|
1. If users are using E2E password mode, then the file sizes are compared on the **encrypted sizes**, rather than the original unencripted file sizes. The reasons are: the encrypted ones are in transferations, and the encrypted sizes can be computed from unencrypted sizes but not the reverse.
|
||||||
|
|
||||||
|
2. Assuming the file A, is already synced between local device and remote service before.
|
||||||
|
|
||||||
|
- If the local size and remote size are both below the threshold, then the file can be synced normally.
|
||||||
|
- If the local size and remote size are both above the threshold, then the file will be ignored normally.
|
||||||
|
- If the local size is below the threshold, and the remote size is above the threshold, then the plugin **rejects** the sync, and throws the error to the user.
|
||||||
|
- If the local size is above the threshold, and the remote size is below the threshold, then the plugin **rejects** the sync, and throws the error to the user.
|
||||||
|
- When it somes to deletions, the same rules apply.
|
||||||
|
|
||||||
|
The main point is that, if the file sizes "cross the line", the plugin does not introduce any further trouble and just reject to work for this file.
|
||||||
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"id": "remotely-save",
|
"id": "remotely-save",
|
||||||
"name": "Remotely Save",
|
"name": "Remotely Save",
|
||||||
"version": "0.3.20",
|
"version": "0.3.25",
|
||||||
"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,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "remotely-save",
|
"name": "remotely-save",
|
||||||
"version": "0.3.20",
|
"version": "0.3.25",
|
||||||
"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",
|
"dev2": "node esbuild.config.mjs",
|
||||||
|
|||||||
+15
-1
@@ -85,6 +85,7 @@ export interface RemotelySavePluginSettings {
|
|||||||
syncUnderscoreItems?: boolean;
|
syncUnderscoreItems?: boolean;
|
||||||
lang?: LangTypeAndAuto;
|
lang?: LangTypeAndAuto;
|
||||||
logToDB?: boolean;
|
logToDB?: boolean;
|
||||||
|
skipSizeLargerThan?: number;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @deprecated
|
* @deprecated
|
||||||
@@ -122,13 +123,24 @@ type DecisionTypeForFile =
|
|||||||
| "uploadLocalToRemote" // "skipLocal && uploadLocalToRemote && cleanLocalDelHist && cleanRemoteDelHist"
|
| "uploadLocalToRemote" // "skipLocal && uploadLocalToRemote && cleanLocalDelHist && cleanRemoteDelHist"
|
||||||
| "downloadRemoteToLocal"; // "downloadRemoteToLocal && skipRemote && cleanLocalDelHist && cleanRemoteDelHist"
|
| "downloadRemoteToLocal"; // "downloadRemoteToLocal && skipRemote && cleanLocalDelHist && cleanRemoteDelHist"
|
||||||
|
|
||||||
|
type DecisionTypeForFileSize =
|
||||||
|
| "skipUploadingTooLarge"
|
||||||
|
| "skipDownloadingTooLarge"
|
||||||
|
| "skipUsingLocalDelTooLarge"
|
||||||
|
| "skipUsingRemoteDelTooLarge"
|
||||||
|
| "errorLocalTooLargeConflictRemote"
|
||||||
|
| "errorRemoteTooLargeConflictLocal";
|
||||||
|
|
||||||
type DecisionTypeForFolder =
|
type DecisionTypeForFolder =
|
||||||
| "createFolder"
|
| "createFolder"
|
||||||
| "uploadLocalDelHistToRemoteFolder"
|
| "uploadLocalDelHistToRemoteFolder"
|
||||||
| "keepRemoteDelHistFolder"
|
| "keepRemoteDelHistFolder"
|
||||||
| "skipFolder";
|
| "skipFolder";
|
||||||
|
|
||||||
export type DecisionType = DecisionTypeForFile | DecisionTypeForFolder;
|
export type DecisionType =
|
||||||
|
| DecisionTypeForFile
|
||||||
|
| DecisionTypeForFileSize
|
||||||
|
| DecisionTypeForFolder;
|
||||||
|
|
||||||
export interface FileOrFolderMixedState {
|
export interface FileOrFolderMixedState {
|
||||||
key: string;
|
key: string;
|
||||||
@@ -139,7 +151,9 @@ export interface FileOrFolderMixedState {
|
|||||||
deltimeLocal?: number;
|
deltimeLocal?: number;
|
||||||
deltimeRemote?: number;
|
deltimeRemote?: number;
|
||||||
sizeLocal?: number;
|
sizeLocal?: number;
|
||||||
|
sizeLocalEnc?: number;
|
||||||
sizeRemote?: number;
|
sizeRemote?: number;
|
||||||
|
sizeRemoteEnc?: number;
|
||||||
changeRemoteMtimeUsingMapping?: boolean;
|
changeRemoteMtimeUsingMapping?: boolean;
|
||||||
changeLocalMtimeUsingMapping?: boolean;
|
changeLocalMtimeUsingMapping?: boolean;
|
||||||
decision?: DecisionType;
|
decision?: DecisionType;
|
||||||
|
|||||||
@@ -26,11 +26,13 @@ const turnSyncPlanToTable = (record: string) => {
|
|||||||
"remoteEncryptedKey",
|
"remoteEncryptedKey",
|
||||||
"existLocal",
|
"existLocal",
|
||||||
"sizeLocal",
|
"sizeLocal",
|
||||||
|
"sizeLocalEnc",
|
||||||
"mtimeLocal",
|
"mtimeLocal",
|
||||||
"deltimeLocal",
|
"deltimeLocal",
|
||||||
"changeLocalMtimeUsingMapping",
|
"changeLocalMtimeUsingMapping",
|
||||||
"existRemote",
|
"existRemote",
|
||||||
"sizeRemote",
|
"sizeRemote",
|
||||||
|
"sizeRemoteEnc",
|
||||||
"mtimeRemote",
|
"mtimeRemote",
|
||||||
"deltimeRemote",
|
"deltimeRemote",
|
||||||
"changeRemoteMtimeUsingMapping",
|
"changeRemoteMtimeUsingMapping",
|
||||||
|
|||||||
+7
-5
@@ -166,18 +166,20 @@ export const decryptBase64urlToString = async (
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const getSizeFromOrigToEnc = (x: number) => {
|
export const getSizeFromOrigToEnc = (x: number) => {
|
||||||
if (x < 0 || !Number.isInteger(x)) {
|
if (x < 0 || Number.isNaN(x) || !Number.isInteger(x)) {
|
||||||
throw Error(`x=${x} is not a valid size`);
|
throw Error(`getSizeFromOrigToEnc: x=${x} is not a valid size`);
|
||||||
}
|
}
|
||||||
return (Math.floor(x / 16) + 1) * 16 + 16;
|
return (Math.floor(x / 16) + 1) * 16 + 16;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getSizeFromEncToOrig = (x: number) => {
|
export const getSizeFromEncToOrig = (x: number) => {
|
||||||
if (x < 32 || !Number.isInteger(x)) {
|
if (x < 32 || Number.isNaN(x) || !Number.isInteger(x)) {
|
||||||
throw Error(`${x} is not a valid size`);
|
throw Error(`getSizeFromEncToOrig: ${x} is not a valid size`);
|
||||||
}
|
}
|
||||||
if (x % 16 !== 0) {
|
if (x % 16 !== 0) {
|
||||||
throw Error(`${x} is not a valid encrypted file size`);
|
throw Error(
|
||||||
|
`getSizeFromEncToOrig: ${x} is not a valid encrypted file size`
|
||||||
|
);
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
minSize: ((x - 16) / 16 - 1) * 16,
|
minSize: ((x - 16) / 16 - 1) * 16,
|
||||||
|
|||||||
+1
-1
Submodule src/langs updated: 82d0619761...42eab5d544
+2
-2
@@ -5,7 +5,7 @@ import { requireApiVersion, TAbstractFile, TFile, TFolder } from "obsidian";
|
|||||||
|
|
||||||
import { API_VER_STAT_FOLDER, SUPPORTED_SERVICES_TYPE } from "./baseTypes";
|
import { API_VER_STAT_FOLDER, SUPPORTED_SERVICES_TYPE } from "./baseTypes";
|
||||||
import type { SyncPlanType } from "./sync";
|
import type { SyncPlanType } from "./sync";
|
||||||
import { toText, unixTimeToStr } from "./misc";
|
import { statFix, toText, unixTimeToStr } from "./misc";
|
||||||
|
|
||||||
import { log } from "./moreOnLog";
|
import { log } from "./moreOnLog";
|
||||||
|
|
||||||
@@ -417,7 +417,7 @@ export const insertRenameRecordByVault = async (
|
|||||||
if (requireApiVersion(API_VER_STAT_FOLDER)) {
|
if (requireApiVersion(API_VER_STAT_FOLDER)) {
|
||||||
// TAbstractFile does not contain these info
|
// TAbstractFile does not contain these info
|
||||||
// but from API_VER_STAT_FOLDER we can manually stat them by path.
|
// but from API_VER_STAT_FOLDER we can manually stat them by path.
|
||||||
const s = await fileOrFolder.vault.adapter.stat(fileOrFolder.path);
|
const s = await statFix(fileOrFolder.vault, fileOrFolder.path);
|
||||||
ctime = s.ctime;
|
ctime = s.ctime;
|
||||||
mtime = s.mtime;
|
mtime = s.mtime;
|
||||||
}
|
}
|
||||||
|
|||||||
+15
-1
@@ -10,6 +10,7 @@ import {
|
|||||||
import cloneDeep from "lodash/cloneDeep";
|
import cloneDeep from "lodash/cloneDeep";
|
||||||
import { createElement, RotateCcw, RefreshCcw, FileText } from "lucide";
|
import { createElement, RotateCcw, RefreshCcw, FileText } from "lucide";
|
||||||
import type {
|
import type {
|
||||||
|
FileOrFolderMixedState,
|
||||||
RemotelySavePluginSettings,
|
RemotelySavePluginSettings,
|
||||||
SyncTriggerSourceType,
|
SyncTriggerSourceType,
|
||||||
} from "./baseTypes";
|
} from "./baseTypes";
|
||||||
@@ -64,6 +65,7 @@ import {
|
|||||||
exportVaultLoggerOutputToFiles,
|
exportVaultLoggerOutputToFiles,
|
||||||
exportVaultSyncPlansToFiles,
|
exportVaultSyncPlansToFiles,
|
||||||
} from "./debugMode";
|
} from "./debugMode";
|
||||||
|
import { SizesConflictModal } from "./syncSizesConflictNotice";
|
||||||
|
|
||||||
const DEFAULT_SETTINGS: RemotelySavePluginSettings = {
|
const DEFAULT_SETTINGS: RemotelySavePluginSettings = {
|
||||||
s3: DEFAULT_S3_CONFIG,
|
s3: DEFAULT_S3_CONFIG,
|
||||||
@@ -82,6 +84,7 @@ const DEFAULT_SETTINGS: RemotelySavePluginSettings = {
|
|||||||
syncUnderscoreItems: false,
|
syncUnderscoreItems: false,
|
||||||
lang: "auto",
|
lang: "auto",
|
||||||
logToDB: false,
|
logToDB: false,
|
||||||
|
skipSizeLargerThan: -1,
|
||||||
};
|
};
|
||||||
|
|
||||||
interface OAuth2Info {
|
interface OAuth2Info {
|
||||||
@@ -280,7 +283,7 @@ export default class RemotelySavePlugin extends Plugin {
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
this.syncStatus = "generating_plan";
|
this.syncStatus = "generating_plan";
|
||||||
const { plan, sortedKeys, deletions } = await getSyncPlan(
|
const { plan, sortedKeys, deletions, sizesGoWrong } = await getSyncPlan(
|
||||||
remoteStates,
|
remoteStates,
|
||||||
local,
|
local,
|
||||||
localConfigDirContents,
|
localConfigDirContents,
|
||||||
@@ -292,6 +295,7 @@ export default class RemotelySavePlugin extends Plugin {
|
|||||||
this.settings.syncConfigDir,
|
this.settings.syncConfigDir,
|
||||||
this.app.vault.configDir,
|
this.app.vault.configDir,
|
||||||
this.settings.syncUnderscoreItems,
|
this.settings.syncUnderscoreItems,
|
||||||
|
this.settings.skipSizeLargerThan,
|
||||||
this.settings.password
|
this.settings.password
|
||||||
);
|
);
|
||||||
log.info(plan.mixedStates); // for debugging
|
log.info(plan.mixedStates); // for debugging
|
||||||
@@ -317,10 +321,20 @@ export default class RemotelySavePlugin extends Plugin {
|
|||||||
sortedKeys,
|
sortedKeys,
|
||||||
metadataFile,
|
metadataFile,
|
||||||
origMetadataOnRemote,
|
origMetadataOnRemote,
|
||||||
|
sizesGoWrong,
|
||||||
deletions,
|
deletions,
|
||||||
(key: string) => self.trash(key),
|
(key: string) => self.trash(key),
|
||||||
this.settings.password,
|
this.settings.password,
|
||||||
this.settings.concurrency,
|
this.settings.concurrency,
|
||||||
|
(ss: FileOrFolderMixedState[]) => {
|
||||||
|
new SizesConflictModal(
|
||||||
|
self.app,
|
||||||
|
self,
|
||||||
|
this.settings.skipSizeLargerThan,
|
||||||
|
ss,
|
||||||
|
this.settings.password !== ""
|
||||||
|
).open();
|
||||||
|
},
|
||||||
(i: number, totalCount: number, pathName: string, decision: string) =>
|
(i: number, totalCount: number, pathName: string, decision: string) =>
|
||||||
self.setCurrSyncMsg(i, totalCount, pathName, decision)
|
self.setCurrSyncMsg(i, totalCount, pathName, decision)
|
||||||
);
|
);
|
||||||
|
|||||||
+23
-4
@@ -339,12 +339,9 @@ export const checkHasSpecialCharForDir = (x: string) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const unixTimeToStr = (x: number | undefined | null) => {
|
export const unixTimeToStr = (x: number | undefined | null) => {
|
||||||
if (x === undefined) {
|
if (x === undefined || x === null || Number.isNaN(x)) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
if (x === null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return window.moment(x).format() as string;
|
return window.moment(x).format() as string;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -408,3 +405,25 @@ export const toText = (x: any) => {
|
|||||||
return `${x}`;
|
return `${x}`;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* On Android the stat has bugs for folders. So we need a fixed version.
|
||||||
|
* @param vault
|
||||||
|
* @param path
|
||||||
|
*/
|
||||||
|
export const statFix = async (vault: Vault, path: string) => {
|
||||||
|
const s = await vault.adapter.stat(path);
|
||||||
|
if (s.ctime === undefined || s.ctime === null || Number.isNaN(s.ctime)) {
|
||||||
|
s.ctime = undefined;
|
||||||
|
}
|
||||||
|
if (s.mtime === undefined || s.mtime === null || Number.isNaN(s.mtime)) {
|
||||||
|
s.mtime = undefined;
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
(s.size === undefined || s.size === null || Number.isNaN(s.size)) &&
|
||||||
|
s.type === "folder"
|
||||||
|
) {
|
||||||
|
s.size = 0;
|
||||||
|
}
|
||||||
|
return s;
|
||||||
|
};
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { Vault, Stat, ListedFiles } from "obsidian";
|
|||||||
import { Queue } from "@fyears/tsqueue";
|
import { Queue } from "@fyears/tsqueue";
|
||||||
import chunk from "lodash/chunk";
|
import chunk from "lodash/chunk";
|
||||||
import flatten from "lodash/flatten";
|
import flatten from "lodash/flatten";
|
||||||
|
import { statFix } from "./misc";
|
||||||
|
|
||||||
export interface ObsConfigDirFileType {
|
export interface ObsConfigDirFileType {
|
||||||
key: string;
|
key: string;
|
||||||
@@ -12,7 +13,7 @@ export interface ObsConfigDirFileType {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const isFolderToSkip = (x: string) => {
|
const isFolderToSkip = (x: string) => {
|
||||||
let specialFolders = [".git", ".svn", "node_modules"];
|
let specialFolders = [".git", ".github", ".gitlab", ".svn", "node_modules"];
|
||||||
for (const iterator of specialFolders) {
|
for (const iterator of specialFolders) {
|
||||||
if (
|
if (
|
||||||
x === iterator ||
|
x === iterator ||
|
||||||
@@ -75,7 +76,8 @@ export const listFilesInObsFolder = async (
|
|||||||
const itemsToFetchChunks = chunk(itemsToFetch, CHUNK_SIZE);
|
const itemsToFetchChunks = chunk(itemsToFetch, CHUNK_SIZE);
|
||||||
for (const singleChunk of itemsToFetchChunks) {
|
for (const singleChunk of itemsToFetchChunks) {
|
||||||
const r = singleChunk.map(async (x) => {
|
const r = singleChunk.map(async (x) => {
|
||||||
const statRes = await vault.adapter.stat(x);
|
const statRes = await statFix(vault, x);
|
||||||
|
|
||||||
const isFolder = statRes.type === "folder";
|
const isFolder = statRes.type === "folder";
|
||||||
let children: ListedFiles = undefined;
|
let children: ListedFiles = undefined;
|
||||||
if (isFolder) {
|
if (isFolder) {
|
||||||
|
|||||||
@@ -371,9 +371,13 @@ export class WrappedDropboxClient {
|
|||||||
throw Error("The user has not manually auth yet.");
|
throw Error("The user has not manually auth yet.");
|
||||||
}
|
}
|
||||||
const currentTs = Date.now();
|
const currentTs = Date.now();
|
||||||
|
const customHeaders = {
|
||||||
|
"Cache-Control": "no-cache",
|
||||||
|
};
|
||||||
if (this.dropboxConfig.accessTokenExpiresAtTime > currentTs) {
|
if (this.dropboxConfig.accessTokenExpiresAtTime > currentTs) {
|
||||||
this.dropbox = new Dropbox({
|
this.dropbox = new Dropbox({
|
||||||
accessToken: this.dropboxConfig.accessToken,
|
accessToken: this.dropboxConfig.accessToken,
|
||||||
|
customHeaders: customHeaders,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
if (this.dropboxConfig.refreshToken === "") {
|
if (this.dropboxConfig.refreshToken === "") {
|
||||||
@@ -393,6 +397,7 @@ export class WrappedDropboxClient {
|
|||||||
);
|
);
|
||||||
this.dropbox = new Dropbox({
|
this.dropbox = new Dropbox({
|
||||||
accessToken: this.dropboxConfig.accessToken,
|
accessToken: this.dropboxConfig.accessToken,
|
||||||
|
customHeaders: customHeaders,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -444,6 +444,7 @@ export class WrappedOnedriveClient {
|
|||||||
contentType: "application/json",
|
contentType: "application/json",
|
||||||
headers: {
|
headers: {
|
||||||
Authorization: `Bearer ${await this.authGetter.getAccessToken()}`,
|
Authorization: `Bearer ${await this.authGetter.getAccessToken()}`,
|
||||||
|
"Cache-Control": "no-cache",
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
@@ -565,6 +566,7 @@ export class WrappedOnedriveClient {
|
|||||||
headers: {
|
headers: {
|
||||||
// no "Content-Length" allowed here
|
// no "Content-Length" allowed here
|
||||||
"Content-Range": `bytes ${rangeStart}-${rangeEnd - 1}/${size}`,
|
"Content-Range": `bytes ${rangeStart}-${rangeEnd - 1}/${size}`,
|
||||||
|
/* "Cache-Control": "no-cache", not allowed here!!! */
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
return res.json as DriveItem | UploadSession;
|
return res.json as DriveItem | UploadSession;
|
||||||
@@ -576,6 +578,7 @@ export class WrappedOnedriveClient {
|
|||||||
"Content-Length": `${rangeEnd - rangeStart}`,
|
"Content-Length": `${rangeEnd - rangeStart}`,
|
||||||
"Content-Range": `bytes ${rangeStart}-${rangeEnd - 1}/${size}`,
|
"Content-Range": `bytes ${rangeStart}-${rangeEnd - 1}/${size}`,
|
||||||
"Content-Type": DEFAULT_CONTENT_TYPE,
|
"Content-Type": DEFAULT_CONTENT_TYPE,
|
||||||
|
/* "Cache-Control": "no-cache", not allowed here!!! */
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
return (await res.json()) as DriveItem | UploadSession;
|
return (await res.json()) as DriveItem | UploadSession;
|
||||||
@@ -809,10 +812,16 @@ const downloadFromRemoteRaw = async (
|
|||||||
);
|
);
|
||||||
const downloadUrl: string = rsp["@microsoft.graph.downloadUrl"];
|
const downloadUrl: string = rsp["@microsoft.graph.downloadUrl"];
|
||||||
if (VALID_REQURL) {
|
if (VALID_REQURL) {
|
||||||
const content = (await requestUrl({ url: downloadUrl })).arrayBuffer;
|
const content = (
|
||||||
|
await requestUrl({
|
||||||
|
url: downloadUrl,
|
||||||
|
headers: { "Cache-Control": "no-cache" },
|
||||||
|
})
|
||||||
|
).arrayBuffer;
|
||||||
return content;
|
return content;
|
||||||
} else {
|
} else {
|
||||||
const content = await (await fetch(downloadUrl)).arrayBuffer();
|
const content = await // cannot set no-cache here, will have cors error
|
||||||
|
(await fetch(downloadUrl)).arrayBuffer();
|
||||||
return content;
|
return content;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
+28
-9
@@ -24,6 +24,7 @@ import { Buffer } from "buffer";
|
|||||||
import * as mime from "mime-types";
|
import * as mime from "mime-types";
|
||||||
import { Vault, requestUrl, RequestUrlParam } from "obsidian";
|
import { Vault, requestUrl, RequestUrlParam } from "obsidian";
|
||||||
import { Readable } from "stream";
|
import { Readable } from "stream";
|
||||||
|
import AggregateError from "aggregate-error";
|
||||||
import {
|
import {
|
||||||
DEFAULT_CONTENT_TYPE,
|
DEFAULT_CONTENT_TYPE,
|
||||||
RemoteItem,
|
RemoteItem,
|
||||||
@@ -97,11 +98,6 @@ class ObsHttpHandler extends FetchHttpHandler {
|
|||||||
contentType = transformedHeaders["content-type"];
|
contentType = transformedHeaders["content-type"];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (transformedHeaders["cache-control"] === undefined) {
|
|
||||||
// every time is a new request
|
|
||||||
transformedHeaders["cache-control"] = "no-cache";
|
|
||||||
}
|
|
||||||
|
|
||||||
let transformedBody: any = body;
|
let transformedBody: any = body;
|
||||||
if (ArrayBuffer.isView(body)) {
|
if (ArrayBuffer.isView(body)) {
|
||||||
transformedBody = bufferToArrayBuffer(body);
|
transformedBody = bufferToArrayBuffer(body);
|
||||||
@@ -200,8 +196,10 @@ export const getS3Client = (s3Config: S3Config) => {
|
|||||||
endpoint = `https://${endpoint}`;
|
endpoint = `https://${endpoint}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let s3Client: S3Client;
|
||||||
|
|
||||||
if (VALID_REQURL && s3Config.bypassCorsLocally) {
|
if (VALID_REQURL && s3Config.bypassCorsLocally) {
|
||||||
const s3Client = new S3Client({
|
s3Client = new S3Client({
|
||||||
region: s3Config.s3Region,
|
region: s3Config.s3Region,
|
||||||
endpoint: endpoint,
|
endpoint: endpoint,
|
||||||
forcePathStyle: s3Config.forcePathStyle,
|
forcePathStyle: s3Config.forcePathStyle,
|
||||||
@@ -211,9 +209,8 @@ export const getS3Client = (s3Config: S3Config) => {
|
|||||||
},
|
},
|
||||||
requestHandler: new ObsHttpHandler(),
|
requestHandler: new ObsHttpHandler(),
|
||||||
});
|
});
|
||||||
return s3Client;
|
|
||||||
} else {
|
} else {
|
||||||
const s3Client = new S3Client({
|
s3Client = new S3Client({
|
||||||
region: s3Config.s3Region,
|
region: s3Config.s3Region,
|
||||||
endpoint: endpoint,
|
endpoint: endpoint,
|
||||||
forcePathStyle: s3Config.forcePathStyle,
|
forcePathStyle: s3Config.forcePathStyle,
|
||||||
@@ -222,8 +219,19 @@ export const getS3Client = (s3Config: S3Config) => {
|
|||||||
secretAccessKey: s3Config.s3SecretAccessKey,
|
secretAccessKey: s3Config.s3SecretAccessKey,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
return s3Client;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
s3Client.middlewareStack.add(
|
||||||
|
(next, context) => (args) => {
|
||||||
|
(args.request as any).headers["cache-control"] = "no-cache";
|
||||||
|
return next(args);
|
||||||
|
},
|
||||||
|
{
|
||||||
|
step: "build",
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return s3Client;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getRemoteMeta = async (
|
export const getRemoteMeta = async (
|
||||||
@@ -530,8 +538,19 @@ export const checkConnectivity = async (
|
|||||||
} catch (err) {
|
} catch (err) {
|
||||||
log.debug(err);
|
log.debug(err);
|
||||||
if (callbackFunc !== undefined) {
|
if (callbackFunc !== undefined) {
|
||||||
|
if (s3Config.s3Endpoint.contains(s3Config.s3BucketName)) {
|
||||||
|
const err2 = new AggregateError([
|
||||||
|
err,
|
||||||
|
new Error(
|
||||||
|
"Maybe you've included the bucket name inside the endpoint setting. Please remove the bucket name and try again."
|
||||||
|
),
|
||||||
|
]);
|
||||||
|
callbackFunc(err2);
|
||||||
|
} else {
|
||||||
callbackFunc(err);
|
callbackFunc(err);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -183,6 +183,9 @@ export class WrappedWebdavClient {
|
|||||||
|
|
||||||
init = async () => {
|
init = async () => {
|
||||||
// init client if not inited
|
// init client if not inited
|
||||||
|
const headers = {
|
||||||
|
"Cache-Control": "no-cache",
|
||||||
|
};
|
||||||
if (this.client === undefined) {
|
if (this.client === undefined) {
|
||||||
if (
|
if (
|
||||||
this.webdavConfig.username !== "" &&
|
this.webdavConfig.username !== "" &&
|
||||||
@@ -191,6 +194,7 @@ export class WrappedWebdavClient {
|
|||||||
this.client = createClient(this.webdavConfig.address, {
|
this.client = createClient(this.webdavConfig.address, {
|
||||||
username: this.webdavConfig.username,
|
username: this.webdavConfig.username,
|
||||||
password: this.webdavConfig.password,
|
password: this.webdavConfig.password,
|
||||||
|
headers: headers,
|
||||||
authType:
|
authType:
|
||||||
this.webdavConfig.authType === "digest"
|
this.webdavConfig.authType === "digest"
|
||||||
? AuthType.Digest
|
? AuthType.Digest
|
||||||
@@ -198,7 +202,9 @@ export class WrappedWebdavClient {
|
|||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
log.info("no password");
|
log.info("no password");
|
||||||
this.client = createClient(this.webdavConfig.address);
|
this.client = createClient(this.webdavConfig.address, {
|
||||||
|
headers: headers,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+158
-134
@@ -645,8 +645,9 @@ const wrapTextWithPasswordHide = (text: TextComponent) => {
|
|||||||
hider.innerHTML = eyeOff;
|
hider.innerHTML = eyeOff;
|
||||||
hider.addEventListener("click", (e) => {
|
hider.addEventListener("click", (e) => {
|
||||||
const isText = text.inputEl.getAttribute("type") === "text";
|
const isText = text.inputEl.getAttribute("type") === "text";
|
||||||
text.inputEl.setAttribute("type", isText ? "password" : "text");
|
|
||||||
hider.innerHTML = isText ? eyeOff : eye;
|
hider.innerHTML = isText ? eyeOff : eye;
|
||||||
|
text.inputEl.setAttribute("type", isText ? "password" : "text");
|
||||||
|
text.inputEl.focus();
|
||||||
});
|
});
|
||||||
|
|
||||||
// the init type of text el is password
|
// the init type of text el is password
|
||||||
@@ -674,100 +675,12 @@ export class RemotelySaveSettingTab extends PluginSettingTab {
|
|||||||
containerEl.createEl("h1", { text: "Remotely Save" });
|
containerEl.createEl("h1", { text: "Remotely Save" });
|
||||||
|
|
||||||
//////////////////////////////////////////////////
|
//////////////////////////////////////////////////
|
||||||
// below for general
|
// below for service chooser (part 1/2)
|
||||||
//////////////////////////////////////////////////
|
|
||||||
|
|
||||||
const generalDiv = containerEl.createEl("div");
|
|
||||||
generalDiv.createEl("h2", { text: t("settings_general") });
|
|
||||||
|
|
||||||
const passwordDiv = generalDiv.createEl("div");
|
|
||||||
let newPassword = `${this.plugin.settings.password}`;
|
|
||||||
new Setting(passwordDiv)
|
|
||||||
.setName(t("settings_password"))
|
|
||||||
.setDesc(t("settings_password_desc"))
|
|
||||||
.addText((text) => {
|
|
||||||
wrapTextWithPasswordHide(text);
|
|
||||||
text
|
|
||||||
.setPlaceholder("")
|
|
||||||
.setValue(`${this.plugin.settings.password}`)
|
|
||||||
.onChange(async (value) => {
|
|
||||||
newPassword = value.trim();
|
|
||||||
});
|
|
||||||
})
|
|
||||||
.addButton(async (button) => {
|
|
||||||
button.setButtonText(t("confirm"));
|
|
||||||
button.onClick(async () => {
|
|
||||||
new PasswordModal(this.app, this.plugin, newPassword).open();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
const scheduleDiv = generalDiv.createEl("div");
|
|
||||||
new Setting(scheduleDiv)
|
|
||||||
.setName(t("settings_autorun"))
|
|
||||||
.setDesc(t("settings_autorun_desc"))
|
|
||||||
.addDropdown((dropdown) => {
|
|
||||||
dropdown.addOption("-1", t("settings_autorun_notset"));
|
|
||||||
dropdown.addOption(`${1000 * 60 * 1}`, t("settings_autorun_1min"));
|
|
||||||
dropdown.addOption(`${1000 * 60 * 5}`, t("settings_autorun_5min"));
|
|
||||||
dropdown.addOption(`${1000 * 60 * 10}`, t("settings_autorun_10min"));
|
|
||||||
dropdown.addOption(`${1000 * 60 * 30}`, t("settings_autorun_30min"));
|
|
||||||
|
|
||||||
dropdown
|
|
||||||
.setValue(`${this.plugin.settings.autoRunEveryMilliseconds}`)
|
|
||||||
.onChange(async (val: string) => {
|
|
||||||
const realVal = parseInt(val);
|
|
||||||
this.plugin.settings.autoRunEveryMilliseconds = realVal;
|
|
||||||
await this.plugin.saveSettings();
|
|
||||||
if (
|
|
||||||
(realVal === undefined || realVal === null || realVal <= 0) &&
|
|
||||||
this.plugin.autoRunIntervalID !== undefined
|
|
||||||
) {
|
|
||||||
// clear
|
|
||||||
window.clearInterval(this.plugin.autoRunIntervalID);
|
|
||||||
this.plugin.autoRunIntervalID = undefined;
|
|
||||||
} else if (
|
|
||||||
realVal !== undefined &&
|
|
||||||
realVal !== null &&
|
|
||||||
realVal > 0
|
|
||||||
) {
|
|
||||||
const intervalID = window.setInterval(() => {
|
|
||||||
this.plugin.syncRun("auto");
|
|
||||||
}, realVal);
|
|
||||||
this.plugin.autoRunIntervalID = intervalID;
|
|
||||||
this.plugin.registerInterval(intervalID);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
const runOnceStartUpDiv = generalDiv.createEl("div");
|
|
||||||
new Setting(runOnceStartUpDiv)
|
|
||||||
.setName(t("settings_runoncestartup"))
|
|
||||||
.setDesc(t("settings_runoncestartup_desc"))
|
|
||||||
.addDropdown((dropdown) => {
|
|
||||||
dropdown.addOption("-1", t("settings_runoncestartup_notset"));
|
|
||||||
dropdown.addOption(
|
|
||||||
`${1000 * 10 * 1}`,
|
|
||||||
t("settings_runoncestartup_10sec")
|
|
||||||
);
|
|
||||||
dropdown.addOption(
|
|
||||||
`${1000 * 30 * 1}`,
|
|
||||||
t("settings_runoncestartup_30sec")
|
|
||||||
);
|
|
||||||
dropdown
|
|
||||||
.setValue(`${this.plugin.settings.initRunAfterMilliseconds}`)
|
|
||||||
.onChange(async (val: string) => {
|
|
||||||
const realVal = parseInt(val);
|
|
||||||
this.plugin.settings.initRunAfterMilliseconds = realVal;
|
|
||||||
await this.plugin.saveSettings();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
//////////////////////////////////////////////////
|
|
||||||
// below for general chooser (part 1/2)
|
|
||||||
//////////////////////////////////////////////////
|
//////////////////////////////////////////////////
|
||||||
|
|
||||||
// we need to create the div in advance of any other service divs
|
// we need to create the div in advance of any other service divs
|
||||||
const serviceChooserDiv = generalDiv.createEl("div");
|
const serviceChooserDiv = containerEl.createDiv();
|
||||||
|
serviceChooserDiv.createEl("h2", { text: t("settings_chooseservice") });
|
||||||
|
|
||||||
//////////////////////////////////////////////////
|
//////////////////////////////////////////////////
|
||||||
// below for s3
|
// below for s3
|
||||||
@@ -777,27 +690,29 @@ export class RemotelySaveSettingTab extends PluginSettingTab {
|
|||||||
s3Div.toggleClass("s3-hide", this.plugin.settings.serviceType !== "s3");
|
s3Div.toggleClass("s3-hide", this.plugin.settings.serviceType !== "s3");
|
||||||
s3Div.createEl("h2", { text: t("settings_s3") });
|
s3Div.createEl("h2", { text: t("settings_s3") });
|
||||||
|
|
||||||
|
const s3LongDescDiv = s3Div.createEl("div", { cls: "settings-long-desc" });
|
||||||
|
|
||||||
for (const c of [
|
for (const c of [
|
||||||
t("settings_s3_disclaimer1"),
|
t("settings_s3_disclaimer1"),
|
||||||
t("settings_s3_disclaimer2"),
|
t("settings_s3_disclaimer2"),
|
||||||
]) {
|
]) {
|
||||||
s3Div.createEl("p", {
|
s3LongDescDiv.createEl("p", {
|
||||||
text: c,
|
text: c,
|
||||||
cls: "s3-disclaimer",
|
cls: "s3-disclaimer",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!VALID_REQURL) {
|
if (!VALID_REQURL) {
|
||||||
s3Div.createEl("p", {
|
s3LongDescDiv.createEl("p", {
|
||||||
text: t("settings_s3_cors"),
|
text: t("settings_s3_cors"),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
s3Div.createEl("p", {
|
s3LongDescDiv.createEl("p", {
|
||||||
text: t("settings_s3_prod"),
|
text: t("settings_s3_prod"),
|
||||||
});
|
});
|
||||||
|
|
||||||
const s3LinksUl = s3Div.createEl("div").createEl("ul");
|
const s3LinksUl = s3LongDescDiv.createEl("ul");
|
||||||
|
|
||||||
s3LinksUl.createEl("li").createEl("a", {
|
s3LinksUl.createEl("li").createEl("a", {
|
||||||
href: "https://docs.aws.amazon.com/general/latest/gr/s3.html",
|
href: "https://docs.aws.amazon.com/general/latest/gr/s3.html",
|
||||||
@@ -930,8 +845,7 @@ export class RemotelySaveSettingTab extends PluginSettingTab {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const partsConcurrencyDiv = s3Div.createEl("div");
|
new Setting(s3Div)
|
||||||
new Setting(partsConcurrencyDiv)
|
|
||||||
.setName(t("settings_s3_parts"))
|
.setName(t("settings_s3_parts"))
|
||||||
.setDesc(t("settings_s3_parts_desc"))
|
.setDesc(t("settings_s3_parts_desc"))
|
||||||
.addDropdown((dropdown) => {
|
.addDropdown((dropdown) => {
|
||||||
@@ -983,16 +897,20 @@ export class RemotelySaveSettingTab extends PluginSettingTab {
|
|||||||
this.plugin.settings.serviceType !== "dropbox"
|
this.plugin.settings.serviceType !== "dropbox"
|
||||||
);
|
);
|
||||||
dropboxDiv.createEl("h2", { text: t("settings_dropbox") });
|
dropboxDiv.createEl("h2", { text: t("settings_dropbox") });
|
||||||
|
|
||||||
|
const dropboxLongDescDiv = dropboxDiv.createEl("div", {
|
||||||
|
cls: "settings-long-desc",
|
||||||
|
});
|
||||||
for (const c of [
|
for (const c of [
|
||||||
t("settings_dropbox_disclaimer1"),
|
t("settings_dropbox_disclaimer1"),
|
||||||
t("settings_dropbox_disclaimer2"),
|
t("settings_dropbox_disclaimer2"),
|
||||||
]) {
|
]) {
|
||||||
dropboxDiv.createEl("p", {
|
dropboxLongDescDiv.createEl("p", {
|
||||||
text: c,
|
text: c,
|
||||||
cls: "dropbox-disclaimer",
|
cls: "dropbox-disclaimer",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
dropboxDiv.createEl("p", {
|
dropboxLongDescDiv.createEl("p", {
|
||||||
text: t("settings_dropbox_folder", {
|
text: t("settings_dropbox_folder", {
|
||||||
pluginID: this.plugin.manifest.id,
|
pluginID: this.plugin.manifest.id,
|
||||||
remoteBaseDir:
|
remoteBaseDir:
|
||||||
@@ -1003,10 +921,10 @@ export class RemotelySaveSettingTab extends PluginSettingTab {
|
|||||||
|
|
||||||
const dropboxSelectAuthDiv = dropboxDiv.createDiv();
|
const dropboxSelectAuthDiv = dropboxDiv.createDiv();
|
||||||
const dropboxAuthDiv = dropboxSelectAuthDiv.createDiv({
|
const dropboxAuthDiv = dropboxSelectAuthDiv.createDiv({
|
||||||
cls: "dropbox-auth-button-hide",
|
cls: "dropbox-auth-button-hide settings-auth-related",
|
||||||
});
|
});
|
||||||
const dropboxRevokeAuthDiv = dropboxSelectAuthDiv.createDiv({
|
const dropboxRevokeAuthDiv = dropboxSelectAuthDiv.createDiv({
|
||||||
cls: "dropbox-revoke-auth-button-hide",
|
cls: "dropbox-revoke-auth-button-hide settings-auth-related",
|
||||||
});
|
});
|
||||||
|
|
||||||
const dropboxRevokeAuthSetting = new Setting(dropboxRevokeAuthDiv)
|
const dropboxRevokeAuthSetting = new Setting(dropboxRevokeAuthDiv)
|
||||||
@@ -1169,17 +1087,20 @@ export class RemotelySaveSettingTab extends PluginSettingTab {
|
|||||||
this.plugin.settings.serviceType !== "onedrive"
|
this.plugin.settings.serviceType !== "onedrive"
|
||||||
);
|
);
|
||||||
onedriveDiv.createEl("h2", { text: t("settings_onedrive") });
|
onedriveDiv.createEl("h2", { text: t("settings_onedrive") });
|
||||||
|
const onedriveLongDescDiv = onedriveDiv.createEl("div", {
|
||||||
|
cls: "settings-long-desc",
|
||||||
|
});
|
||||||
for (const c of [
|
for (const c of [
|
||||||
t("settings_onedrive_disclaimer1"),
|
t("settings_onedrive_disclaimer1"),
|
||||||
t("settings_onedrive_disclaimer2"),
|
t("settings_onedrive_disclaimer2"),
|
||||||
]) {
|
]) {
|
||||||
onedriveDiv.createEl("p", {
|
onedriveLongDescDiv.createEl("p", {
|
||||||
text: c,
|
text: c,
|
||||||
cls: "onedrive-disclaimer",
|
cls: "onedrive-disclaimer",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
onedriveDiv.createEl("p", {
|
onedriveLongDescDiv.createEl("p", {
|
||||||
text: t("settings_onedrive_folder", {
|
text: t("settings_onedrive_folder", {
|
||||||
pluginID: this.plugin.manifest.id,
|
pluginID: this.plugin.manifest.id,
|
||||||
remoteBaseDir:
|
remoteBaseDir:
|
||||||
@@ -1188,16 +1109,16 @@ export class RemotelySaveSettingTab extends PluginSettingTab {
|
|||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
onedriveDiv.createEl("p", {
|
onedriveLongDescDiv.createEl("p", {
|
||||||
text: t("settings_onedrive_nobiz"),
|
text: t("settings_onedrive_nobiz"),
|
||||||
});
|
});
|
||||||
|
|
||||||
const onedriveSelectAuthDiv = onedriveDiv.createDiv();
|
const onedriveSelectAuthDiv = onedriveDiv.createDiv();
|
||||||
const onedriveAuthDiv = onedriveSelectAuthDiv.createDiv({
|
const onedriveAuthDiv = onedriveSelectAuthDiv.createDiv({
|
||||||
cls: "onedrive-auth-button-hide",
|
cls: "onedrive-auth-button-hide settings-auth-related",
|
||||||
});
|
});
|
||||||
const onedriveRevokeAuthDiv = onedriveSelectAuthDiv.createDiv({
|
const onedriveRevokeAuthDiv = onedriveSelectAuthDiv.createDiv({
|
||||||
cls: "onedrive-revoke-auth-button-hide",
|
cls: "onedrive-revoke-auth-button-hide settings-auth-related",
|
||||||
});
|
});
|
||||||
|
|
||||||
const onedriveRevokeAuthSetting = new Setting(onedriveRevokeAuthDiv)
|
const onedriveRevokeAuthSetting = new Setting(onedriveRevokeAuthDiv)
|
||||||
@@ -1317,22 +1238,26 @@ export class RemotelySaveSettingTab extends PluginSettingTab {
|
|||||||
|
|
||||||
webdavDiv.createEl("h2", { text: t("settings_webdav") });
|
webdavDiv.createEl("h2", { text: t("settings_webdav") });
|
||||||
|
|
||||||
webdavDiv.createEl("p", {
|
const webdavLongDescDiv = webdavDiv.createEl("div", {
|
||||||
|
cls: "settings-long-desc",
|
||||||
|
});
|
||||||
|
|
||||||
|
webdavLongDescDiv.createEl("p", {
|
||||||
text: t("settings_webdav_disclaimer1"),
|
text: t("settings_webdav_disclaimer1"),
|
||||||
cls: "webdav-disclaimer",
|
cls: "webdav-disclaimer",
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!VALID_REQURL) {
|
if (!VALID_REQURL) {
|
||||||
webdavDiv.createEl("p", {
|
webdavLongDescDiv.createEl("p", {
|
||||||
text: t("settings_webdav_cors_os"),
|
text: t("settings_webdav_cors_os"),
|
||||||
});
|
});
|
||||||
|
|
||||||
webdavDiv.createEl("p", {
|
webdavLongDescDiv.createEl("p", {
|
||||||
text: t("settings_webdav_cors"),
|
text: t("settings_webdav_cors"),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
webdavDiv.createEl("p", {
|
webdavLongDescDiv.createEl("p", {
|
||||||
text: t("settings_webdav_folder", {
|
text: t("settings_webdav_folder", {
|
||||||
remoteBaseDir:
|
remoteBaseDir:
|
||||||
this.plugin.settings.webdav.remoteBaseDir || this.app.vault.getName(),
|
this.plugin.settings.webdav.remoteBaseDir || this.app.vault.getName(),
|
||||||
@@ -1564,6 +1489,113 @@ export class RemotelySaveSettingTab extends PluginSettingTab {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////
|
||||||
|
// below for basic settings
|
||||||
|
//////////////////////////////////////////////////
|
||||||
|
|
||||||
|
const basicDiv = containerEl.createEl("div");
|
||||||
|
basicDiv.createEl("h2", { text: t("settings_basic") });
|
||||||
|
|
||||||
|
let newPassword = `${this.plugin.settings.password}`;
|
||||||
|
new Setting(basicDiv)
|
||||||
|
.setName(t("settings_password"))
|
||||||
|
.setDesc(t("settings_password_desc"))
|
||||||
|
.addText((text) => {
|
||||||
|
wrapTextWithPasswordHide(text);
|
||||||
|
text
|
||||||
|
.setPlaceholder("")
|
||||||
|
.setValue(`${this.plugin.settings.password}`)
|
||||||
|
.onChange(async (value) => {
|
||||||
|
newPassword = value.trim();
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.addButton(async (button) => {
|
||||||
|
button.setButtonText(t("confirm"));
|
||||||
|
button.onClick(async () => {
|
||||||
|
new PasswordModal(this.app, this.plugin, newPassword).open();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
new Setting(basicDiv)
|
||||||
|
.setName(t("settings_autorun"))
|
||||||
|
.setDesc(t("settings_autorun_desc"))
|
||||||
|
.addDropdown((dropdown) => {
|
||||||
|
dropdown.addOption("-1", t("settings_autorun_notset"));
|
||||||
|
dropdown.addOption(`${1000 * 60 * 1}`, t("settings_autorun_1min"));
|
||||||
|
dropdown.addOption(`${1000 * 60 * 5}`, t("settings_autorun_5min"));
|
||||||
|
dropdown.addOption(`${1000 * 60 * 10}`, t("settings_autorun_10min"));
|
||||||
|
dropdown.addOption(`${1000 * 60 * 30}`, t("settings_autorun_30min"));
|
||||||
|
|
||||||
|
dropdown
|
||||||
|
.setValue(`${this.plugin.settings.autoRunEveryMilliseconds}`)
|
||||||
|
.onChange(async (val: string) => {
|
||||||
|
const realVal = parseInt(val);
|
||||||
|
this.plugin.settings.autoRunEveryMilliseconds = realVal;
|
||||||
|
await this.plugin.saveSettings();
|
||||||
|
if (
|
||||||
|
(realVal === undefined || realVal === null || realVal <= 0) &&
|
||||||
|
this.plugin.autoRunIntervalID !== undefined
|
||||||
|
) {
|
||||||
|
// clear
|
||||||
|
window.clearInterval(this.plugin.autoRunIntervalID);
|
||||||
|
this.plugin.autoRunIntervalID = undefined;
|
||||||
|
} else if (
|
||||||
|
realVal !== undefined &&
|
||||||
|
realVal !== null &&
|
||||||
|
realVal > 0
|
||||||
|
) {
|
||||||
|
const intervalID = window.setInterval(() => {
|
||||||
|
this.plugin.syncRun("auto");
|
||||||
|
}, realVal);
|
||||||
|
this.plugin.autoRunIntervalID = intervalID;
|
||||||
|
this.plugin.registerInterval(intervalID);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
new Setting(basicDiv)
|
||||||
|
.setName(t("settings_runoncestartup"))
|
||||||
|
.setDesc(t("settings_runoncestartup_desc"))
|
||||||
|
.addDropdown((dropdown) => {
|
||||||
|
dropdown.addOption("-1", t("settings_runoncestartup_notset"));
|
||||||
|
dropdown.addOption(
|
||||||
|
`${1000 * 1 * 1}`,
|
||||||
|
t("settings_runoncestartup_1sec")
|
||||||
|
);
|
||||||
|
dropdown.addOption(
|
||||||
|
`${1000 * 10 * 1}`,
|
||||||
|
t("settings_runoncestartup_10sec")
|
||||||
|
);
|
||||||
|
dropdown.addOption(
|
||||||
|
`${1000 * 30 * 1}`,
|
||||||
|
t("settings_runoncestartup_30sec")
|
||||||
|
);
|
||||||
|
dropdown
|
||||||
|
.setValue(`${this.plugin.settings.initRunAfterMilliseconds}`)
|
||||||
|
.onChange(async (val: string) => {
|
||||||
|
const realVal = parseInt(val);
|
||||||
|
this.plugin.settings.initRunAfterMilliseconds = realVal;
|
||||||
|
await this.plugin.saveSettings();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
new Setting(basicDiv)
|
||||||
|
.setName(t("settings_skiplargefiles"))
|
||||||
|
.setDesc(t("settings_skiplargefiles_desc"))
|
||||||
|
.addDropdown((dropdown) => {
|
||||||
|
dropdown.addOption("-1", t("settings_skiplargefiles_notset"));
|
||||||
|
|
||||||
|
const mbs = [1, 5, 10, 50, 100, 500, 1000];
|
||||||
|
for (const mb of mbs) {
|
||||||
|
dropdown.addOption(`${mb * 1000 * 1000}`, `${mb} MB`);
|
||||||
|
}
|
||||||
|
dropdown
|
||||||
|
.setValue(`${this.plugin.settings.skipSizeLargerThan}`)
|
||||||
|
.onChange(async (val) => {
|
||||||
|
this.plugin.settings.skipSizeLargerThan = parseInt(val);
|
||||||
|
await this.plugin.saveSettings();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
//////////////////////////////////////////////////
|
//////////////////////////////////////////////////
|
||||||
// below for advanced settings
|
// below for advanced settings
|
||||||
//////////////////////////////////////////////////
|
//////////////////////////////////////////////////
|
||||||
@@ -1572,8 +1604,7 @@ export class RemotelySaveSettingTab extends PluginSettingTab {
|
|||||||
text: t("settings_adv"),
|
text: t("settings_adv"),
|
||||||
});
|
});
|
||||||
|
|
||||||
const concurrencyDiv = advDiv.createEl("div");
|
new Setting(advDiv)
|
||||||
new Setting(concurrencyDiv)
|
|
||||||
.setName(t("settings_concurrency"))
|
.setName(t("settings_concurrency"))
|
||||||
.setDesc(t("settings_concurrency_desc"))
|
.setDesc(t("settings_concurrency_desc"))
|
||||||
.addDropdown((dropdown) => {
|
.addDropdown((dropdown) => {
|
||||||
@@ -1594,8 +1625,7 @@ export class RemotelySaveSettingTab extends PluginSettingTab {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
const syncUnderscoreItemsDiv = advDiv.createEl("div");
|
new Setting(advDiv)
|
||||||
new Setting(syncUnderscoreItemsDiv)
|
|
||||||
.setName(t("settings_syncunderscore"))
|
.setName(t("settings_syncunderscore"))
|
||||||
.setDesc(t("settings_syncunderscore_desc"))
|
.setDesc(t("settings_syncunderscore_desc"))
|
||||||
.addDropdown((dropdown) => {
|
.addDropdown((dropdown) => {
|
||||||
@@ -1611,8 +1641,7 @@ export class RemotelySaveSettingTab extends PluginSettingTab {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
const syncConfigDirDiv = advDiv.createEl("div");
|
new Setting(advDiv)
|
||||||
new Setting(syncConfigDirDiv)
|
|
||||||
.setName(t("settings_configdir"))
|
.setName(t("settings_configdir"))
|
||||||
.setDesc(
|
.setDesc(
|
||||||
t("settings_configdir_desc", {
|
t("settings_configdir_desc", {
|
||||||
@@ -1676,8 +1705,7 @@ export class RemotelySaveSettingTab extends PluginSettingTab {
|
|||||||
const debugDiv = containerEl.createEl("div");
|
const debugDiv = containerEl.createEl("div");
|
||||||
debugDiv.createEl("h2", { text: t("settings_debug") });
|
debugDiv.createEl("h2", { text: t("settings_debug") });
|
||||||
|
|
||||||
const setConsoleLogLevelDiv = debugDiv.createDiv("div");
|
new Setting(debugDiv)
|
||||||
new Setting(setConsoleLogLevelDiv)
|
|
||||||
.setName(t("settings_debuglevel"))
|
.setName(t("settings_debuglevel"))
|
||||||
.setDesc(t("settings_debuglevel_desc"))
|
.setDesc(t("settings_debuglevel_desc"))
|
||||||
.addDropdown(async (dropdown) => {
|
.addDropdown(async (dropdown) => {
|
||||||
@@ -1692,8 +1720,8 @@ export class RemotelySaveSettingTab extends PluginSettingTab {
|
|||||||
log.info(`the log level is changed to ${val}`);
|
log.info(`the log level is changed to ${val}`);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
const outputCurrSettingsDiv = debugDiv.createDiv("div");
|
|
||||||
new Setting(outputCurrSettingsDiv)
|
new Setting(debugDiv)
|
||||||
.setName(t("settings_outputsettingsconsole"))
|
.setName(t("settings_outputsettingsconsole"))
|
||||||
.setDesc(t("settings_outputsettingsconsole_desc"))
|
.setDesc(t("settings_outputsettingsconsole_desc"))
|
||||||
.addButton(async (button) => {
|
.addButton(async (button) => {
|
||||||
@@ -1704,8 +1732,8 @@ export class RemotelySaveSettingTab extends PluginSettingTab {
|
|||||||
new Notice(t("settings_outputsettingsconsole_notice"));
|
new Notice(t("settings_outputsettingsconsole_notice"));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
const syncPlanDiv = debugDiv.createEl("div");
|
|
||||||
new Setting(syncPlanDiv)
|
new Setting(debugDiv)
|
||||||
.setName(t("settings_syncplans"))
|
.setName(t("settings_syncplans"))
|
||||||
.setDesc(t("settings_syncplans_desc"))
|
.setDesc(t("settings_syncplans_desc"))
|
||||||
.addButton(async (button) => {
|
.addButton(async (button) => {
|
||||||
@@ -1732,7 +1760,7 @@ export class RemotelySaveSettingTab extends PluginSettingTab {
|
|||||||
new Notice(t("settings_syncplans_notice"));
|
new Notice(t("settings_syncplans_notice"));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
new Setting(syncPlanDiv)
|
new Setting(debugDiv)
|
||||||
.setName(t("settings_delsyncplans"))
|
.setName(t("settings_delsyncplans"))
|
||||||
.setDesc(t("settings_delsyncplans_desc"))
|
.setDesc(t("settings_delsyncplans_desc"))
|
||||||
.addButton(async (button) => {
|
.addButton(async (button) => {
|
||||||
@@ -1743,8 +1771,7 @@ export class RemotelySaveSettingTab extends PluginSettingTab {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
const logToDBDiv = debugDiv.createEl("div");
|
new Setting(debugDiv)
|
||||||
new Setting(logToDBDiv)
|
|
||||||
.setName(t("settings_logtodb"))
|
.setName(t("settings_logtodb"))
|
||||||
.setDesc(t("settings_logtodb_desc"))
|
.setDesc(t("settings_logtodb_desc"))
|
||||||
.addDropdown(async (dropdown) => {
|
.addDropdown(async (dropdown) => {
|
||||||
@@ -1771,7 +1798,7 @@ export class RemotelySaveSettingTab extends PluginSettingTab {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
new Setting(logToDBDiv)
|
new Setting(debugDiv)
|
||||||
.setName(t("settings_logtodbexport"))
|
.setName(t("settings_logtodbexport"))
|
||||||
.setDesc(
|
.setDesc(
|
||||||
t("settings_logtodbexport_desc", {
|
t("settings_logtodbexport_desc", {
|
||||||
@@ -1790,7 +1817,7 @@ export class RemotelySaveSettingTab extends PluginSettingTab {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
new Setting(logToDBDiv)
|
new Setting(debugDiv)
|
||||||
.setName(t("settings_logtodbclear"))
|
.setName(t("settings_logtodbclear"))
|
||||||
.setDesc(t("settings_logtodbclear_desc"))
|
.setDesc(t("settings_logtodbclear_desc"))
|
||||||
.addButton(async (button) => {
|
.addButton(async (button) => {
|
||||||
@@ -1801,8 +1828,7 @@ export class RemotelySaveSettingTab extends PluginSettingTab {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
const syncMappingDiv = debugDiv.createEl("div");
|
new Setting(debugDiv)
|
||||||
new Setting(syncMappingDiv)
|
|
||||||
.setName(t("settings_delsyncmap"))
|
.setName(t("settings_delsyncmap"))
|
||||||
.setDesc(t("settings_delsyncmap_desc"))
|
.setDesc(t("settings_delsyncmap_desc"))
|
||||||
.addButton(async (button) => {
|
.addButton(async (button) => {
|
||||||
@@ -1813,8 +1839,7 @@ export class RemotelySaveSettingTab extends PluginSettingTab {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
const outputCurrBasePathVaultIDDiv = debugDiv.createDiv("div");
|
new Setting(debugDiv)
|
||||||
new Setting(outputCurrBasePathVaultIDDiv)
|
|
||||||
.setName(t("settings_outputbasepathvaultid"))
|
.setName(t("settings_outputbasepathvaultid"))
|
||||||
.setDesc(t("settings_outputbasepathvaultid_desc"))
|
.setDesc(t("settings_outputbasepathvaultid_desc"))
|
||||||
.addButton(async (button) => {
|
.addButton(async (button) => {
|
||||||
@@ -1825,8 +1850,7 @@ export class RemotelySaveSettingTab extends PluginSettingTab {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
const dbsResetDiv = debugDiv.createEl("div");
|
new Setting(debugDiv)
|
||||||
new Setting(dbsResetDiv)
|
|
||||||
.setName(t("settings_resetcache"))
|
.setName(t("settings_resetcache"))
|
||||||
.setDesc(t("settings_resetcache_desc"))
|
.setDesc(t("settings_resetcache_desc"))
|
||||||
.addButton(async (button) => {
|
.addButton(async (button) => {
|
||||||
|
|||||||
+265
-22
@@ -19,6 +19,7 @@ import {
|
|||||||
decryptBase32ToString,
|
decryptBase32ToString,
|
||||||
decryptBase64urlToString,
|
decryptBase64urlToString,
|
||||||
encryptStringToBase64url,
|
encryptStringToBase64url,
|
||||||
|
getSizeFromOrigToEnc,
|
||||||
MAGIC_ENCRYPTED_PREFIX_BASE32,
|
MAGIC_ENCRYPTED_PREFIX_BASE32,
|
||||||
MAGIC_ENCRYPTED_PREFIX_BASE64URL,
|
MAGIC_ENCRYPTED_PREFIX_BASE64URL,
|
||||||
} from "./encrypt";
|
} from "./encrypt";
|
||||||
@@ -36,6 +37,7 @@ import {
|
|||||||
getParentFolder,
|
getParentFolder,
|
||||||
atWhichLevel,
|
atWhichLevel,
|
||||||
unixTimeToStr,
|
unixTimeToStr,
|
||||||
|
statFix,
|
||||||
} from "./misc";
|
} from "./misc";
|
||||||
import { RemoteClient } from "./remote";
|
import { RemoteClient } from "./remote";
|
||||||
import {
|
import {
|
||||||
@@ -217,22 +219,29 @@ export const parseRemoteItems = async (
|
|||||||
if (backwardMapping !== undefined) {
|
if (backwardMapping !== undefined) {
|
||||||
key = backwardMapping.localKey;
|
key = backwardMapping.localKey;
|
||||||
const mtimeRemote = backwardMapping.localMtime || entry.lastModified;
|
const mtimeRemote = backwardMapping.localMtime || entry.lastModified;
|
||||||
|
|
||||||
|
// the backwardMapping.localSize is the file BEFORE encryption
|
||||||
|
// we want to split two sizes for comparation later
|
||||||
|
|
||||||
r = {
|
r = {
|
||||||
key: key,
|
key: key,
|
||||||
existRemote: true,
|
existRemote: true,
|
||||||
mtimeRemote: mtimeRemote,
|
mtimeRemote: mtimeRemote,
|
||||||
mtimeRemoteFmt: unixTimeToStr(mtimeRemote),
|
mtimeRemoteFmt: unixTimeToStr(mtimeRemote),
|
||||||
sizeRemote: backwardMapping.localSize || entry.size,
|
sizeRemote: backwardMapping.localSize,
|
||||||
|
sizeRemoteEnc: password === "" ? undefined : entry.size,
|
||||||
remoteEncryptedKey: remoteEncryptedKey,
|
remoteEncryptedKey: remoteEncryptedKey,
|
||||||
changeRemoteMtimeUsingMapping: true,
|
changeRemoteMtimeUsingMapping: true,
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
|
// do not have backwardMapping
|
||||||
r = {
|
r = {
|
||||||
key: key,
|
key: key,
|
||||||
existRemote: true,
|
existRemote: true,
|
||||||
mtimeRemote: entry.lastModified,
|
mtimeRemote: entry.lastModified,
|
||||||
mtimeRemoteFmt: unixTimeToStr(entry.lastModified),
|
mtimeRemoteFmt: unixTimeToStr(entry.lastModified),
|
||||||
sizeRemote: entry.size,
|
sizeRemote: password === "" ? entry.size : undefined,
|
||||||
|
sizeRemoteEnc: password === "" ? undefined : entry.size,
|
||||||
remoteEncryptedKey: remoteEncryptedKey,
|
remoteEncryptedKey: remoteEncryptedKey,
|
||||||
changeRemoteMtimeUsingMapping: false,
|
changeRemoteMtimeUsingMapping: false,
|
||||||
};
|
};
|
||||||
@@ -305,7 +314,8 @@ const ensembleMixedStates = async (
|
|||||||
localFileHistory: FileFolderHistoryRecord[],
|
localFileHistory: FileFolderHistoryRecord[],
|
||||||
syncConfigDir: boolean,
|
syncConfigDir: boolean,
|
||||||
configDir: string,
|
configDir: string,
|
||||||
syncUnderscoreItems: boolean
|
syncUnderscoreItems: boolean,
|
||||||
|
password: string
|
||||||
) => {
|
) => {
|
||||||
const results = {} as Record<string, FileOrFolderMixedState>;
|
const results = {} as Record<string, FileOrFolderMixedState>;
|
||||||
|
|
||||||
@@ -327,13 +337,15 @@ const ensembleMixedStates = async (
|
|||||||
// ignore
|
// ignore
|
||||||
continue;
|
continue;
|
||||||
} else if (entry instanceof TFile) {
|
} else if (entry instanceof TFile) {
|
||||||
const mtimeLocal = Math.max(entry.stat.mtime || 0, entry.stat.ctime || 0);
|
const mtimeLocal = Math.max(entry.stat.mtime ?? 0, entry.stat.ctime ?? 0);
|
||||||
r = {
|
r = {
|
||||||
key: entry.path,
|
key: entry.path,
|
||||||
existLocal: true,
|
existLocal: true,
|
||||||
mtimeLocal: mtimeLocal,
|
mtimeLocal: mtimeLocal,
|
||||||
mtimeLocalFmt: unixTimeToStr(mtimeLocal),
|
mtimeLocalFmt: unixTimeToStr(mtimeLocal),
|
||||||
sizeLocal: entry.stat.size,
|
sizeLocal: entry.stat.size,
|
||||||
|
sizeLocalEnc:
|
||||||
|
password === "" ? undefined : getSizeFromOrigToEnc(entry.stat.size),
|
||||||
};
|
};
|
||||||
} else if (entry instanceof TFolder) {
|
} else if (entry instanceof TFolder) {
|
||||||
key = `${entry.path}/`;
|
key = `${entry.path}/`;
|
||||||
@@ -343,6 +355,7 @@ const ensembleMixedStates = async (
|
|||||||
mtimeLocal: undefined,
|
mtimeLocal: undefined,
|
||||||
mtimeLocalFmt: undefined,
|
mtimeLocalFmt: undefined,
|
||||||
sizeLocal: 0,
|
sizeLocal: 0,
|
||||||
|
sizeLocalEnc: password === "" ? undefined : getSizeFromOrigToEnc(0),
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
throw Error(`unexpected ${entry}`);
|
throw Error(`unexpected ${entry}`);
|
||||||
@@ -358,6 +371,7 @@ const ensembleMixedStates = async (
|
|||||||
results[key].mtimeLocal = r.mtimeLocal;
|
results[key].mtimeLocal = r.mtimeLocal;
|
||||||
results[key].mtimeLocalFmt = r.mtimeLocalFmt;
|
results[key].mtimeLocalFmt = r.mtimeLocalFmt;
|
||||||
results[key].sizeLocal = r.sizeLocal;
|
results[key].sizeLocal = r.sizeLocal;
|
||||||
|
results[key].sizeLocalEnc = r.sizeLocalEnc;
|
||||||
} else {
|
} else {
|
||||||
results[key] = r;
|
results[key] = r;
|
||||||
results[key].existRemote = false;
|
results[key].existRemote = false;
|
||||||
@@ -367,13 +381,18 @@ const ensembleMixedStates = async (
|
|||||||
if (syncConfigDir && localConfigDirContents !== undefined) {
|
if (syncConfigDir && localConfigDirContents !== undefined) {
|
||||||
for (const entry of localConfigDirContents) {
|
for (const entry of localConfigDirContents) {
|
||||||
const key = entry.key;
|
const key = entry.key;
|
||||||
const mtimeLocal = Math.max(entry.mtime, entry.ctime);
|
let mtimeLocal = Math.max(entry.mtime ?? 0, entry.ctime ?? 0);
|
||||||
|
if (Number.isNaN(mtimeLocal) || mtimeLocal === 0) {
|
||||||
|
mtimeLocal = undefined;
|
||||||
|
}
|
||||||
const r: FileOrFolderMixedState = {
|
const r: FileOrFolderMixedState = {
|
||||||
key: key,
|
key: key,
|
||||||
existLocal: true,
|
existLocal: true,
|
||||||
mtimeLocal: mtimeLocal,
|
mtimeLocal: mtimeLocal,
|
||||||
mtimeLocalFmt: unixTimeToStr(mtimeLocal),
|
mtimeLocalFmt: unixTimeToStr(mtimeLocal),
|
||||||
sizeLocal: entry.size,
|
sizeLocal: entry.size,
|
||||||
|
sizeLocalEnc:
|
||||||
|
password === "" ? undefined : getSizeFromOrigToEnc(entry.size),
|
||||||
};
|
};
|
||||||
|
|
||||||
if (results.hasOwnProperty(key)) {
|
if (results.hasOwnProperty(key)) {
|
||||||
@@ -382,6 +401,7 @@ const ensembleMixedStates = async (
|
|||||||
results[key].mtimeLocal = r.mtimeLocal;
|
results[key].mtimeLocal = r.mtimeLocal;
|
||||||
results[key].mtimeLocalFmt = r.mtimeLocalFmt;
|
results[key].mtimeLocalFmt = r.mtimeLocalFmt;
|
||||||
results[key].sizeLocal = r.sizeLocal;
|
results[key].sizeLocal = r.sizeLocal;
|
||||||
|
results[key].sizeLocalEnc = r.sizeLocalEnc;
|
||||||
} else {
|
} else {
|
||||||
results[key] = r;
|
results[key] = r;
|
||||||
results[key].existRemote = false;
|
results[key].existRemote = false;
|
||||||
@@ -452,10 +472,13 @@ const ensembleMixedStates = async (
|
|||||||
changeLocalMtimeUsingMapping: true,
|
changeLocalMtimeUsingMapping: true,
|
||||||
};
|
};
|
||||||
if (results.hasOwnProperty(key)) {
|
if (results.hasOwnProperty(key)) {
|
||||||
const mtimeLocal = Math.max(
|
let mtimeLocal = Math.max(
|
||||||
r.mtimeLocal || 0,
|
r.mtimeLocal ?? 0,
|
||||||
results[key].mtimeLocal || 0
|
results[key].mtimeLocal ?? 0
|
||||||
);
|
);
|
||||||
|
if (Number.isNaN(mtimeLocal) || mtimeLocal === 0) {
|
||||||
|
mtimeLocal = undefined;
|
||||||
|
}
|
||||||
results[key].mtimeLocal = mtimeLocal;
|
results[key].mtimeLocal = mtimeLocal;
|
||||||
results[key].mtimeLocalFmt = unixTimeToStr(mtimeLocal);
|
results[key].mtimeLocalFmt = unixTimeToStr(mtimeLocal);
|
||||||
results[key].changeLocalMtimeUsingMapping =
|
results[key].changeLocalMtimeUsingMapping =
|
||||||
@@ -484,6 +507,7 @@ const ensembleMixedStates = async (
|
|||||||
const assignOperationToFileInplace = (
|
const assignOperationToFileInplace = (
|
||||||
origRecord: FileOrFolderMixedState,
|
origRecord: FileOrFolderMixedState,
|
||||||
keptFolder: Set<string>,
|
keptFolder: Set<string>,
|
||||||
|
skipSizeLargerThan: number,
|
||||||
password: string = ""
|
password: string = ""
|
||||||
) => {
|
) => {
|
||||||
let r = origRecord;
|
let r = origRecord;
|
||||||
@@ -526,6 +550,18 @@ const assignOperationToFileInplace = (
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
(r.existLocal && password !== "" && r.sizeLocalEnc === undefined) ||
|
||||||
|
(r.existRemote && password !== "" && r.sizeRemoteEnc === undefined)
|
||||||
|
) {
|
||||||
|
throw new Error(
|
||||||
|
`Error: No encryption sizes: ${JSON.stringify(r, null, 2)}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const sizeLocalComp = password === "" ? r.sizeLocal : r.sizeLocalEnc;
|
||||||
|
const sizeRemoteComp = password === "" ? r.sizeRemote : r.sizeRemoteEnc;
|
||||||
|
|
||||||
// 1. mtimeLocal
|
// 1. mtimeLocal
|
||||||
if (r.existLocal) {
|
if (r.existLocal) {
|
||||||
const mtimeRemote = r.existRemote ? r.mtimeRemote : -1;
|
const mtimeRemote = r.existRemote ? r.mtimeRemote : -1;
|
||||||
@@ -536,26 +572,79 @@ const assignOperationToFileInplace = (
|
|||||||
r.mtimeLocal >= deltimeLocal &&
|
r.mtimeLocal >= deltimeLocal &&
|
||||||
r.mtimeLocal >= deltimeRemote
|
r.mtimeLocal >= deltimeRemote
|
||||||
) {
|
) {
|
||||||
|
if (sizeLocalComp === undefined) {
|
||||||
|
throw new Error(
|
||||||
|
`Error: no local size but has local mtime: ${JSON.stringify(
|
||||||
|
r,
|
||||||
|
null,
|
||||||
|
2
|
||||||
|
)}`
|
||||||
|
);
|
||||||
|
}
|
||||||
if (r.mtimeLocal === r.mtimeRemote) {
|
if (r.mtimeLocal === r.mtimeRemote) {
|
||||||
// mtime the same
|
// local and remote both exist and mtimes are the same
|
||||||
if (password === "") {
|
if (sizeLocalComp === sizeRemoteComp) {
|
||||||
// no password, we can also compare the sizes!
|
// do not need to consider skipSizeLargerThan in this case
|
||||||
if (r.sizeLocal === r.sizeRemote) {
|
|
||||||
r.decision = "skipUploading";
|
r.decision = "skipUploading";
|
||||||
r.decisionBranch = 1;
|
r.decisionBranch = 1;
|
||||||
} else {
|
} else {
|
||||||
|
if (skipSizeLargerThan <= 0) {
|
||||||
r.decision = "uploadLocalToRemote";
|
r.decision = "uploadLocalToRemote";
|
||||||
r.decisionBranch = 2;
|
r.decisionBranch = 2;
|
||||||
|
} else {
|
||||||
|
// limit the sizes
|
||||||
|
if (sizeLocalComp <= skipSizeLargerThan) {
|
||||||
|
if (sizeRemoteComp <= skipSizeLargerThan) {
|
||||||
|
r.decision = "uploadLocalToRemote";
|
||||||
|
r.decisionBranch = 18;
|
||||||
|
} else {
|
||||||
|
r.decision = "errorRemoteTooLargeConflictLocal";
|
||||||
|
r.decisionBranch = 19;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// we have password, then the sizes are always unequal
|
if (sizeRemoteComp <= skipSizeLargerThan) {
|
||||||
// we can only rely on mtime
|
r.decision = "errorLocalTooLargeConflictRemote";
|
||||||
r.decision = "skipUploading";
|
r.decisionBranch = 20;
|
||||||
r.decisionBranch = 3;
|
} else {
|
||||||
|
r.decision = "skipUploadingTooLarge";
|
||||||
|
r.decisionBranch = 21;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
// we have local laregest mtime,
|
||||||
|
// and the remote not existing or smaller mtime
|
||||||
|
if (skipSizeLargerThan <= 0) {
|
||||||
|
// no need to consider sizes
|
||||||
r.decision = "uploadLocalToRemote";
|
r.decision = "uploadLocalToRemote";
|
||||||
r.decisionBranch = 4;
|
r.decisionBranch = 4;
|
||||||
|
} else {
|
||||||
|
// need to consider sizes
|
||||||
|
if (sizeLocalComp <= skipSizeLargerThan) {
|
||||||
|
if (sizeRemoteComp === undefined) {
|
||||||
|
r.decision = "uploadLocalToRemote";
|
||||||
|
r.decisionBranch = 22;
|
||||||
|
} else if (sizeRemoteComp <= skipSizeLargerThan) {
|
||||||
|
r.decision = "uploadLocalToRemote";
|
||||||
|
r.decisionBranch = 23;
|
||||||
|
} else {
|
||||||
|
r.decision = "errorRemoteTooLargeConflictLocal";
|
||||||
|
r.decisionBranch = 24;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (sizeRemoteComp === undefined) {
|
||||||
|
r.decision = "skipUploadingTooLarge";
|
||||||
|
r.decisionBranch = 25;
|
||||||
|
} else if (sizeRemoteComp <= skipSizeLargerThan) {
|
||||||
|
r.decision = "errorLocalTooLargeConflictRemote";
|
||||||
|
r.decisionBranch = 26;
|
||||||
|
} else {
|
||||||
|
r.decision = "skipUploadingTooLarge";
|
||||||
|
r.decisionBranch = 27;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
keptFolder.add(getParentFolder(r.key));
|
keptFolder.add(getParentFolder(r.key));
|
||||||
return r;
|
return r;
|
||||||
@@ -572,8 +661,49 @@ const assignOperationToFileInplace = (
|
|||||||
r.mtimeRemote >= deltimeLocal &&
|
r.mtimeRemote >= deltimeLocal &&
|
||||||
r.mtimeRemote >= deltimeRemote
|
r.mtimeRemote >= deltimeRemote
|
||||||
) {
|
) {
|
||||||
|
// we have remote laregest mtime,
|
||||||
|
// and the local not existing or smaller mtime
|
||||||
|
if (sizeRemoteComp === undefined) {
|
||||||
|
throw new Error(
|
||||||
|
`Error: no remote size but has remote mtime: ${JSON.stringify(
|
||||||
|
r,
|
||||||
|
null,
|
||||||
|
2
|
||||||
|
)}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (skipSizeLargerThan <= 0) {
|
||||||
|
// no need to consider sizes
|
||||||
r.decision = "downloadRemoteToLocal";
|
r.decision = "downloadRemoteToLocal";
|
||||||
r.decisionBranch = 5;
|
r.decisionBranch = 5;
|
||||||
|
} else {
|
||||||
|
// need to consider sizes
|
||||||
|
if (sizeRemoteComp <= skipSizeLargerThan) {
|
||||||
|
if (sizeLocalComp === undefined) {
|
||||||
|
r.decision = "downloadRemoteToLocal";
|
||||||
|
r.decisionBranch = 28;
|
||||||
|
} else if (sizeLocalComp <= skipSizeLargerThan) {
|
||||||
|
r.decision = "downloadRemoteToLocal";
|
||||||
|
r.decisionBranch = 29;
|
||||||
|
} else {
|
||||||
|
r.decision = "errorLocalTooLargeConflictRemote";
|
||||||
|
r.decisionBranch = 30;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (sizeLocalComp === undefined) {
|
||||||
|
r.decision = "skipDownloadingTooLarge";
|
||||||
|
r.decisionBranch = 31;
|
||||||
|
} else if (sizeLocalComp <= skipSizeLargerThan) {
|
||||||
|
r.decision = "errorRemoteTooLargeConflictLocal";
|
||||||
|
r.decisionBranch = 32;
|
||||||
|
} else {
|
||||||
|
r.decision = "skipDownloadingTooLarge";
|
||||||
|
r.decisionBranch = 33;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
keptFolder.add(getParentFolder(r.key));
|
keptFolder.add(getParentFolder(r.key));
|
||||||
return r;
|
return r;
|
||||||
}
|
}
|
||||||
@@ -589,11 +719,45 @@ const assignOperationToFileInplace = (
|
|||||||
r.deltimeLocal >= mtimeRemote &&
|
r.deltimeLocal >= mtimeRemote &&
|
||||||
r.deltimeLocal >= deltimeRemote
|
r.deltimeLocal >= deltimeRemote
|
||||||
) {
|
) {
|
||||||
|
if (skipSizeLargerThan <= 0) {
|
||||||
r.decision = "uploadLocalDelHistToRemote";
|
r.decision = "uploadLocalDelHistToRemote";
|
||||||
r.decisionBranch = 6;
|
r.decisionBranch = 6;
|
||||||
if (r.existLocal || r.existRemote) {
|
if (r.existLocal || r.existRemote) {
|
||||||
// actual deletion would happen
|
// actual deletion would happen
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
const localTooLargeToDelete =
|
||||||
|
r.existLocal && sizeLocalComp > skipSizeLargerThan;
|
||||||
|
const remoteTooLargeToDelete =
|
||||||
|
r.existRemote && sizeRemoteComp > skipSizeLargerThan;
|
||||||
|
if (localTooLargeToDelete) {
|
||||||
|
if (remoteTooLargeToDelete) {
|
||||||
|
r.decision = "skipUsingLocalDelTooLarge";
|
||||||
|
r.decisionBranch = 34;
|
||||||
|
} else {
|
||||||
|
if (r.existRemote) {
|
||||||
|
r.decision = "errorLocalTooLargeConflictRemote";
|
||||||
|
r.decisionBranch = 35;
|
||||||
|
} else {
|
||||||
|
r.decision = "skipUsingLocalDelTooLarge";
|
||||||
|
r.decisionBranch = 36;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (remoteTooLargeToDelete) {
|
||||||
|
if (r.existLocal) {
|
||||||
|
r.decision = "errorLocalTooLargeConflictRemote";
|
||||||
|
r.decisionBranch = 37;
|
||||||
|
} else {
|
||||||
|
r.decision = "skipUsingLocalDelTooLarge";
|
||||||
|
r.decisionBranch = 38;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
r.decision = "uploadLocalDelHistToRemote";
|
||||||
|
r.decisionBranch = 39;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
return r;
|
return r;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -608,11 +772,45 @@ const assignOperationToFileInplace = (
|
|||||||
r.deltimeRemote >= mtimeRemote &&
|
r.deltimeRemote >= mtimeRemote &&
|
||||||
r.deltimeRemote >= deltimeLocal
|
r.deltimeRemote >= deltimeLocal
|
||||||
) {
|
) {
|
||||||
|
if (skipSizeLargerThan <= 0) {
|
||||||
r.decision = "keepRemoteDelHist";
|
r.decision = "keepRemoteDelHist";
|
||||||
r.decisionBranch = 7;
|
r.decisionBranch = 7;
|
||||||
if (r.existLocal || r.existRemote) {
|
if (r.existLocal || r.existRemote) {
|
||||||
// actual deletion would happen
|
// actual deletion would happen
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
const localTooLargeToDelete =
|
||||||
|
r.existLocal && sizeLocalComp > skipSizeLargerThan;
|
||||||
|
const remoteTooLargeToDelete =
|
||||||
|
r.existRemote && sizeRemoteComp > skipSizeLargerThan;
|
||||||
|
if (localTooLargeToDelete) {
|
||||||
|
if (remoteTooLargeToDelete) {
|
||||||
|
r.decision = "skipUsingRemoteDelTooLarge";
|
||||||
|
r.decisionBranch = 40;
|
||||||
|
} else {
|
||||||
|
if (r.existRemote) {
|
||||||
|
r.decision = "errorLocalTooLargeConflictRemote";
|
||||||
|
r.decisionBranch = 41;
|
||||||
|
} else {
|
||||||
|
r.decision = "skipUsingRemoteDelTooLarge";
|
||||||
|
r.decisionBranch = 42;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (remoteTooLargeToDelete) {
|
||||||
|
if (r.existLocal) {
|
||||||
|
r.decision = "errorLocalTooLargeConflictRemote";
|
||||||
|
r.decisionBranch = 43;
|
||||||
|
} else {
|
||||||
|
r.decision = "skipUsingRemoteDelTooLarge";
|
||||||
|
r.decisionBranch = 44;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
r.decision = "keepRemoteDelHist";
|
||||||
|
r.decisionBranch = 45;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
return r;
|
return r;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -647,9 +845,14 @@ const assignOperationToFolderInplace = async (
|
|||||||
// if it was created after deletion, we should keep it as is
|
// if it was created after deletion, we should keep it as is
|
||||||
if (requireApiVersion(API_VER_STAT_FOLDER)) {
|
if (requireApiVersion(API_VER_STAT_FOLDER)) {
|
||||||
if (r.existLocal) {
|
if (r.existLocal) {
|
||||||
const { ctime, mtime } = await vault.adapter.stat(r.key);
|
const { ctime, mtime } = await statFix(vault, r.key);
|
||||||
const cmtime = Math.max(ctime, mtime);
|
const cmtime = Math.max(ctime ?? 0, mtime ?? 0);
|
||||||
if (cmtime > 0 && cmtime >= deltimeLocal && cmtime >= deltimeRemote) {
|
if (
|
||||||
|
!Number.isNaN(cmtime) &&
|
||||||
|
cmtime > 0 &&
|
||||||
|
cmtime >= deltimeLocal &&
|
||||||
|
cmtime >= deltimeRemote
|
||||||
|
) {
|
||||||
keptFolder.add(getParentFolder(r.key));
|
keptFolder.add(getParentFolder(r.key));
|
||||||
if (r.existLocal && r.existRemote) {
|
if (r.existLocal && r.existRemote) {
|
||||||
r.decision = "skipFolder";
|
r.decision = "skipFolder";
|
||||||
@@ -746,6 +949,10 @@ const DELETION_DECISIONS: Set<DecisionType> = new Set([
|
|||||||
"uploadLocalDelHistToRemoteFolder",
|
"uploadLocalDelHistToRemoteFolder",
|
||||||
"keepRemoteDelHistFolder",
|
"keepRemoteDelHistFolder",
|
||||||
]);
|
]);
|
||||||
|
const SIZES_GO_WRONG_DECISIONS: Set<DecisionType> = new Set([
|
||||||
|
"errorLocalTooLargeConflictRemote",
|
||||||
|
"errorRemoteTooLargeConflictLocal",
|
||||||
|
]);
|
||||||
|
|
||||||
export const getSyncPlan = async (
|
export const getSyncPlan = async (
|
||||||
remoteStates: FileOrFolderMixedState[],
|
remoteStates: FileOrFolderMixedState[],
|
||||||
@@ -759,6 +966,7 @@ export const getSyncPlan = async (
|
|||||||
syncConfigDir: boolean,
|
syncConfigDir: boolean,
|
||||||
configDir: string,
|
configDir: string,
|
||||||
syncUnderscoreItems: boolean,
|
syncUnderscoreItems: boolean,
|
||||||
|
skipSizeLargerThan: number,
|
||||||
password: string = ""
|
password: string = ""
|
||||||
) => {
|
) => {
|
||||||
const mixedStates = await ensembleMixedStates(
|
const mixedStates = await ensembleMixedStates(
|
||||||
@@ -769,13 +977,15 @@ export const getSyncPlan = async (
|
|||||||
localFileHistory,
|
localFileHistory,
|
||||||
syncConfigDir,
|
syncConfigDir,
|
||||||
configDir,
|
configDir,
|
||||||
syncUnderscoreItems
|
syncUnderscoreItems,
|
||||||
|
password
|
||||||
);
|
);
|
||||||
|
|
||||||
const sortedKeys = Object.keys(mixedStates).sort(
|
const sortedKeys = Object.keys(mixedStates).sort(
|
||||||
(k1, k2) => k2.length - k1.length
|
(k1, k2) => k2.length - k1.length
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const sizesGoWrong: FileOrFolderMixedState[] = [];
|
||||||
const deletions: DeletionOnRemote[] = [];
|
const deletions: DeletionOnRemote[] = [];
|
||||||
|
|
||||||
const keptFolder = new Set<string>();
|
const keptFolder = new Set<string>();
|
||||||
@@ -791,7 +1001,16 @@ export const getSyncPlan = async (
|
|||||||
} else {
|
} else {
|
||||||
// get all operations of files
|
// get all operations of files
|
||||||
// and at the same time get some helper info for folders
|
// and at the same time get some helper info for folders
|
||||||
assignOperationToFileInplace(val, keptFolder, password);
|
assignOperationToFileInplace(
|
||||||
|
val,
|
||||||
|
keptFolder,
|
||||||
|
skipSizeLargerThan,
|
||||||
|
password
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (SIZES_GO_WRONG_DECISIONS.has(val.decision)) {
|
||||||
|
sizesGoWrong.push(val);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (DELETION_DECISIONS.has(val.decision)) {
|
if (DELETION_DECISIONS.has(val.decision)) {
|
||||||
@@ -834,6 +1053,7 @@ export const getSyncPlan = async (
|
|||||||
plan: plan,
|
plan: plan,
|
||||||
sortedKeys: sortedKeys,
|
sortedKeys: sortedKeys,
|
||||||
deletions: deletions,
|
deletions: deletions,
|
||||||
|
sizesGoWrong: sizesGoWrong,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -1015,6 +1235,14 @@ const dispatchOperationToActual = async (
|
|||||||
await clearDeleteRenameHistoryOfKeyAndVault(db, r.key, vaultRandomID);
|
await clearDeleteRenameHistoryOfKeyAndVault(db, r.key, vaultRandomID);
|
||||||
} else if (r.decision === "skipFolder") {
|
} else if (r.decision === "skipFolder") {
|
||||||
// do nothing!
|
// do nothing!
|
||||||
|
} else if (r.decision === "skipUploadingTooLarge") {
|
||||||
|
// do nothing!
|
||||||
|
} else if (r.decision === "skipDownloadingTooLarge") {
|
||||||
|
// do nothing!
|
||||||
|
} else if (r.decision === "skipUsingLocalDelTooLarge") {
|
||||||
|
// do nothing!
|
||||||
|
} else if (r.decision === "skipUsingRemoteDelTooLarge") {
|
||||||
|
// do nothing!
|
||||||
} else {
|
} else {
|
||||||
throw Error(`unknown decision in ${JSON.stringify(r)}`);
|
throw Error(`unknown decision in ${JSON.stringify(r)}`);
|
||||||
}
|
}
|
||||||
@@ -1033,7 +1261,14 @@ const splitThreeSteps = (syncPlan: SyncPlanType, sortedKeys: string[]) => {
|
|||||||
const key = sortedKeys[i];
|
const key = sortedKeys[i];
|
||||||
const val: FileOrFolderMixedState = Object.assign({}, mixedStates[key]); // copy to avoid issue
|
const val: FileOrFolderMixedState = Object.assign({}, mixedStates[key]); // copy to avoid issue
|
||||||
|
|
||||||
if (val.decision === "skipFolder" || val.decision === "skipUploading") {
|
if (
|
||||||
|
val.decision === "skipFolder" ||
|
||||||
|
val.decision === "skipUploading" ||
|
||||||
|
val.decision === "skipDownloadingTooLarge" ||
|
||||||
|
val.decision === "skipUploadingTooLarge" ||
|
||||||
|
val.decision === "skipUsingLocalDelTooLarge" ||
|
||||||
|
val.decision === "skipUsingRemoteDelTooLarge"
|
||||||
|
) {
|
||||||
// pass
|
// pass
|
||||||
} else if (val.decision === "createFolder") {
|
} else if (val.decision === "createFolder") {
|
||||||
const level = atWhichLevel(key);
|
const level = atWhichLevel(key);
|
||||||
@@ -1093,15 +1328,23 @@ export const doActualSync = async (
|
|||||||
sortedKeys: string[],
|
sortedKeys: string[],
|
||||||
metadataFile: FileOrFolderMixedState,
|
metadataFile: FileOrFolderMixedState,
|
||||||
origMetadata: MetadataOnRemote,
|
origMetadata: MetadataOnRemote,
|
||||||
|
sizesGoWrong: FileOrFolderMixedState[],
|
||||||
deletions: DeletionOnRemote[],
|
deletions: DeletionOnRemote[],
|
||||||
localDeleteFunc: any,
|
localDeleteFunc: any,
|
||||||
password: string = "",
|
password: string = "",
|
||||||
concurrency: number = 1,
|
concurrency: number = 1,
|
||||||
|
callbackSizesGoWrong?: any,
|
||||||
callbackSyncProcess?: any
|
callbackSyncProcess?: any
|
||||||
) => {
|
) => {
|
||||||
const mixedStates = syncPlan.mixedStates;
|
const mixedStates = syncPlan.mixedStates;
|
||||||
const totalCount = sortedKeys.length || 0;
|
const totalCount = sortedKeys.length || 0;
|
||||||
|
|
||||||
|
if (sizesGoWrong.length > 0) {
|
||||||
|
log.debug(`some sizes are larger than the threshold, abort and show hints`);
|
||||||
|
callbackSizesGoWrong(sizesGoWrong);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
log.debug(`start syncing extra data firstly`);
|
log.debug(`start syncing extra data firstly`);
|
||||||
await uploadExtraMeta(
|
await uploadExtraMeta(
|
||||||
client,
|
client,
|
||||||
|
|||||||
@@ -0,0 +1,90 @@
|
|||||||
|
import { App, Modal, Notice, PluginSettingTab, Setting } from "obsidian";
|
||||||
|
import type RemotelySavePlugin from "./main"; // unavoidable
|
||||||
|
import type { TransItemType } from "./i18n";
|
||||||
|
import type { FileOrFolderMixedState } from "./baseTypes";
|
||||||
|
|
||||||
|
import { log } from "./moreOnLog";
|
||||||
|
|
||||||
|
export class SizesConflictModal extends Modal {
|
||||||
|
readonly plugin: RemotelySavePlugin;
|
||||||
|
readonly skipSizeLargerThan: number;
|
||||||
|
readonly sizesGoWrong: FileOrFolderMixedState[];
|
||||||
|
readonly hasPassword: boolean;
|
||||||
|
constructor(
|
||||||
|
app: App,
|
||||||
|
plugin: RemotelySavePlugin,
|
||||||
|
skipSizeLargerThan: number,
|
||||||
|
sizesGoWrong: FileOrFolderMixedState[],
|
||||||
|
hasPassword: boolean
|
||||||
|
) {
|
||||||
|
super(app);
|
||||||
|
this.plugin = plugin;
|
||||||
|
this.skipSizeLargerThan = skipSizeLargerThan;
|
||||||
|
this.sizesGoWrong = sizesGoWrong;
|
||||||
|
this.hasPassword = hasPassword;
|
||||||
|
}
|
||||||
|
onOpen() {
|
||||||
|
let { contentEl } = this;
|
||||||
|
const t = (x: TransItemType, vars?: any) => {
|
||||||
|
return this.plugin.i18n.t(x, vars);
|
||||||
|
};
|
||||||
|
|
||||||
|
contentEl.createEl("h2", {
|
||||||
|
text: t("modal_sizesconflict_title"),
|
||||||
|
});
|
||||||
|
|
||||||
|
t("modal_sizesconflict_desc", {
|
||||||
|
thresholdMB: `${this.skipSizeLargerThan / 1000 / 1000}`,
|
||||||
|
thresholdBytes: `${this.skipSizeLargerThan}`,
|
||||||
|
})
|
||||||
|
.split("\n")
|
||||||
|
.forEach((val) => {
|
||||||
|
contentEl.createEl("p", { text: val });
|
||||||
|
});
|
||||||
|
|
||||||
|
const info = this.serialize();
|
||||||
|
|
||||||
|
contentEl.createDiv().createEl(
|
||||||
|
"button",
|
||||||
|
{
|
||||||
|
text: t("modal_sizesconflict_copybutton"),
|
||||||
|
},
|
||||||
|
(el) => {
|
||||||
|
el.onclick = async () => {
|
||||||
|
await navigator.clipboard.writeText(info);
|
||||||
|
new Notice(t("modal_sizesconflict_copynotice"));
|
||||||
|
};
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
contentEl.createEl("pre", {
|
||||||
|
text: info,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
serialize() {
|
||||||
|
return this.sizesGoWrong
|
||||||
|
.map((x) => {
|
||||||
|
return [
|
||||||
|
x.key,
|
||||||
|
this.hasPassword
|
||||||
|
? `encrypted name: ${x.remoteEncryptedKey}`
|
||||||
|
: undefined,
|
||||||
|
`local ${this.hasPassword ? "encrypted " : ""}bytes: ${
|
||||||
|
this.hasPassword ? x.sizeLocalEnc : x.sizeLocal
|
||||||
|
}`,
|
||||||
|
`remote ${this.hasPassword ? "encrypted " : ""}bytes: ${
|
||||||
|
this.hasPassword ? x.sizeRemoteEnc : x.sizeRemote
|
||||||
|
}`,
|
||||||
|
]
|
||||||
|
.filter((tmp) => tmp !== undefined)
|
||||||
|
.join("\n");
|
||||||
|
})
|
||||||
|
.join("\n\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
onClose() {
|
||||||
|
let { contentEl } = this;
|
||||||
|
contentEl.empty();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,6 +8,11 @@
|
|||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.settings-auth-related {
|
||||||
|
border-top: 1px solid var(--background-modifier-border);
|
||||||
|
padding-top: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
.s3-disclaimer {
|
.s3-disclaimer {
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
{
|
{
|
||||||
"0.3.2": "0.12.15",
|
"0.3.2": "0.12.15",
|
||||||
"0.3.20": "0.13.21"
|
"0.3.25": "0.13.21"
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user