mirror of
https://github.com/remotely-save/remotely-save.git
synced 2024-06-07 21:10:45 +00:00
check password using walkPartial instead of cache
This commit is contained in:
parent
b584f89a95
commit
63c54d1956
@ -3,6 +3,7 @@ import type { Entity } from "./baseTypes";
|
|||||||
export abstract class FakeFs {
|
export abstract class FakeFs {
|
||||||
abstract kind: string;
|
abstract kind: string;
|
||||||
abstract walk(): Promise<Entity[]>;
|
abstract walk(): Promise<Entity[]>;
|
||||||
|
abstract walkPartial(): Promise<Entity[]>;
|
||||||
abstract stat(key: string): Promise<Entity>;
|
abstract stat(key: string): Promise<Entity>;
|
||||||
abstract mkdir(key: string, mtime?: number, ctime?: number): Promise<Entity>;
|
abstract mkdir(key: string, mtime?: number, ctime?: number): Promise<Entity>;
|
||||||
abstract writeFile(
|
abstract writeFile(
|
||||||
|
@ -452,13 +452,21 @@ export class FakeFsDropbox extends FakeFs {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async walk(): Promise<Entity[]> {
|
async walk(): Promise<Entity[]> {
|
||||||
|
return await this._walk(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
async walkPartial(): Promise<Entity[]> {
|
||||||
|
return await this._walk(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
async _walk(partial: boolean): Promise<Entity[]> {
|
||||||
await this._init();
|
await this._init();
|
||||||
|
|
||||||
let res = await this.dropbox.filesListFolder({
|
let res = await this.dropbox.filesListFolder({
|
||||||
path: `/${this.remoteBaseDir}`,
|
path: `/${this.remoteBaseDir}`,
|
||||||
recursive: true,
|
recursive: !partial,
|
||||||
include_deleted: false,
|
include_deleted: false,
|
||||||
limit: 1000,
|
limit: partial ? 10 : 1000,
|
||||||
});
|
});
|
||||||
if (res.status !== 200) {
|
if (res.status !== 200) {
|
||||||
throw Error(JSON.stringify(res));
|
throw Error(JSON.stringify(res));
|
||||||
@ -471,20 +479,22 @@ export class FakeFsDropbox extends FakeFs {
|
|||||||
.filter((x) => x.path_display !== `/${this.remoteBaseDir}`)
|
.filter((x) => x.path_display !== `/${this.remoteBaseDir}`)
|
||||||
.map((x) => fromDropboxItemToEntity(x, this.remoteBaseDir));
|
.map((x) => fromDropboxItemToEntity(x, this.remoteBaseDir));
|
||||||
|
|
||||||
while (res.result.has_more) {
|
if (!partial) {
|
||||||
res = await this.dropbox.filesListFolderContinue({
|
while (res.result.has_more) {
|
||||||
cursor: res.result.cursor,
|
res = await this.dropbox.filesListFolderContinue({
|
||||||
});
|
cursor: res.result.cursor,
|
||||||
if (res.status !== 200) {
|
});
|
||||||
throw Error(JSON.stringify(res));
|
if (res.status !== 200) {
|
||||||
}
|
throw Error(JSON.stringify(res));
|
||||||
|
}
|
||||||
|
|
||||||
const contents2 = res.result.entries;
|
const contents2 = res.result.entries;
|
||||||
const unifiedContents2 = contents2
|
const unifiedContents2 = contents2
|
||||||
.filter((x) => x[".tag"] !== "deleted")
|
.filter((x) => x[".tag"] !== "deleted")
|
||||||
.filter((x) => x.path_display !== `/${this.remoteBaseDir}`)
|
.filter((x) => x.path_display !== `/${this.remoteBaseDir}`)
|
||||||
.map((x) => fromDropboxItemToEntity(x, this.remoteBaseDir));
|
.map((x) => fromDropboxItemToEntity(x, this.remoteBaseDir));
|
||||||
unifiedContents.push(...unifiedContents2);
|
unifiedContents.push(...unifiedContents2);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fixEntityListCasesInplace(unifiedContents);
|
fixEntityListCasesInplace(unifiedContents);
|
||||||
|
@ -78,8 +78,6 @@ export class FakeFsEncrypt extends FakeFs {
|
|||||||
cacheMapOrigToEnc: Record<string, string>;
|
cacheMapOrigToEnc: Record<string, string>;
|
||||||
hasCacheMap: boolean;
|
hasCacheMap: boolean;
|
||||||
kind: string;
|
kind: string;
|
||||||
innerWalkResultCache?: Entity[];
|
|
||||||
innerWalkResultCacheTime?: number;
|
|
||||||
|
|
||||||
constructor(innerFs: FakeFs, password: string, method: CipherMethodType) {
|
constructor(innerFs: FakeFs, password: string, method: CipherMethodType) {
|
||||||
super();
|
super();
|
||||||
@ -110,26 +108,8 @@ export class FakeFsEncrypt extends FakeFs {
|
|||||||
throw Error(`no idea about isFolderAware for method=${this.method}`);
|
throw Error(`no idea about isFolderAware for method=${this.method}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* we want a little caching here.
|
|
||||||
*/
|
|
||||||
async _getInnerWalkResult(): Promise<Entity[]> {
|
|
||||||
let innerWalkResult: Entity[] | undefined = undefined;
|
|
||||||
if (
|
|
||||||
this.innerWalkResultCacheTime !== undefined &&
|
|
||||||
this.innerWalkResultCacheTime >= Date.now() - 1000
|
|
||||||
) {
|
|
||||||
innerWalkResult = this.innerWalkResultCache!;
|
|
||||||
} else {
|
|
||||||
innerWalkResult = await this.innerFs.walk();
|
|
||||||
this.innerWalkResultCache = innerWalkResult;
|
|
||||||
this.innerWalkResultCacheTime = Date.now();
|
|
||||||
}
|
|
||||||
return innerWalkResult;
|
|
||||||
}
|
|
||||||
|
|
||||||
async isPasswordOk(): Promise<PasswordCheckType> {
|
async isPasswordOk(): Promise<PasswordCheckType> {
|
||||||
const innerWalkResult = await this._getInnerWalkResult();
|
const innerWalkResult = await this.walkPartial();
|
||||||
|
|
||||||
if (innerWalkResult === undefined || innerWalkResult.length === 0) {
|
if (innerWalkResult === undefined || innerWalkResult.length === 0) {
|
||||||
// remote empty
|
// remote empty
|
||||||
@ -186,8 +166,16 @@ export class FakeFsEncrypt extends FakeFs {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async walk(): Promise<Entity[]> {
|
async walk(): Promise<Entity[]> {
|
||||||
const innerWalkResult = await this._getInnerWalkResult();
|
const innerWalkResult = await this.innerFs.walk();
|
||||||
|
return await this._dealWithWalk(innerWalkResult);
|
||||||
|
}
|
||||||
|
|
||||||
|
async walkPartial(): Promise<Entity[]> {
|
||||||
|
const innerWalkResult = await this.innerFs.walkPartial();
|
||||||
|
return await this._dealWithWalk(innerWalkResult);
|
||||||
|
}
|
||||||
|
|
||||||
|
async _dealWithWalk(innerWalkResult: Entity[]): Promise<Entity[]> {
|
||||||
const res: Entity[] = [];
|
const res: Entity[] = [];
|
||||||
|
|
||||||
if (this.isPasswordEmpty()) {
|
if (this.isPasswordEmpty()) {
|
||||||
|
@ -109,6 +109,10 @@ export class FakeFsLocal extends FakeFs {
|
|||||||
return local;
|
return local;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async walkPartial(): Promise<Entity[]> {
|
||||||
|
return await this.walk();
|
||||||
|
}
|
||||||
|
|
||||||
async stat(key: string): Promise<Entity> {
|
async stat(key: string): Promise<Entity> {
|
||||||
const statRes = await statFix(this.vault, key);
|
const statRes = await statFix(this.vault, key);
|
||||||
if (statRes === undefined || statRes === null) {
|
if (statRes === undefined || statRes === null) {
|
||||||
|
@ -13,6 +13,10 @@ export class FakeFsMock extends FakeFs {
|
|||||||
throw new Error("Method not implemented.");
|
throw new Error("Method not implemented.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async walkPartial(): Promise<Entity[]> {
|
||||||
|
return await this.walk();
|
||||||
|
}
|
||||||
|
|
||||||
async stat(key: string): Promise<Entity> {
|
async stat(key: string): Promise<Entity> {
|
||||||
throw new Error("Method not implemented.");
|
throw new Error("Method not implemented.");
|
||||||
}
|
}
|
||||||
|
@ -682,6 +682,31 @@ export class FakeFsOnedrive extends FakeFs {
|
|||||||
return unifiedContents;
|
return unifiedContents;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async walkPartial(): Promise<Entity[]> {
|
||||||
|
await this._init();
|
||||||
|
|
||||||
|
const DELTA_LINK_KEY = "@odata.deltaLink";
|
||||||
|
|
||||||
|
const res = await this._getJson(
|
||||||
|
`/drive/special/approot:/${this.remoteBaseDir}:/delta`
|
||||||
|
);
|
||||||
|
const driveItems = res.value as DriveItem[];
|
||||||
|
// console.debug(driveItems);
|
||||||
|
|
||||||
|
// lastly we should have delta link?
|
||||||
|
if (DELTA_LINK_KEY in res) {
|
||||||
|
this.onedriveConfig.deltaLink = res[DELTA_LINK_KEY];
|
||||||
|
await this.saveUpdatedConfigFunc();
|
||||||
|
}
|
||||||
|
|
||||||
|
// unify everything to Entity
|
||||||
|
const unifiedContents = driveItems
|
||||||
|
.map((x) => fromDriveItemToEntity(x, this.remoteBaseDir))
|
||||||
|
.filter((x) => x.key !== "/");
|
||||||
|
|
||||||
|
return unifiedContents;
|
||||||
|
}
|
||||||
|
|
||||||
async stat(key: string): Promise<Entity> {
|
async stat(key: string): Promise<Entity> {
|
||||||
await this._init();
|
await this._init();
|
||||||
return await this._statFromRoot(getOnedrivePath(key, this.remoteBaseDir));
|
return await this._statFromRoot(getOnedrivePath(key, this.remoteBaseDir));
|
||||||
|
43
src/fsS3.ts
43
src/fsS3.ts
@ -399,9 +399,16 @@ export class FakeFsS3 extends FakeFs {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async walk(): Promise<Entity[]> {
|
async walk(): Promise<Entity[]> {
|
||||||
const res = (await this._walkFromRoot(this.s3Config.remotePrefix)).filter(
|
const res = (
|
||||||
(x) => x.key !== "" && x.key !== "/"
|
await this._walkFromRoot(this.s3Config.remotePrefix, false)
|
||||||
);
|
).filter((x) => x.key !== "" && x.key !== "/");
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
async walkPartial(): Promise<Entity[]> {
|
||||||
|
const res = (
|
||||||
|
await this._walkFromRoot(this.s3Config.remotePrefix, true)
|
||||||
|
).filter((x) => x.key !== "" && x.key !== "/");
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -409,19 +416,23 @@ export class FakeFsS3 extends FakeFs {
|
|||||||
* the input key contains basedir (prefix),
|
* the input key contains basedir (prefix),
|
||||||
* but the result doesn't contain it.
|
* but the result doesn't contain it.
|
||||||
*/
|
*/
|
||||||
async _walkFromRoot(prefixOfRawKeys: string | undefined) {
|
async _walkFromRoot(prefixOfRawKeys: string | undefined, partial: boolean) {
|
||||||
const confCmd = {
|
const confCmd = {
|
||||||
Bucket: this.s3Config.s3BucketName,
|
Bucket: this.s3Config.s3BucketName,
|
||||||
} as ListObjectsV2CommandInput;
|
} as ListObjectsV2CommandInput;
|
||||||
if (prefixOfRawKeys !== undefined && prefixOfRawKeys !== "") {
|
if (prefixOfRawKeys !== undefined && prefixOfRawKeys !== "") {
|
||||||
confCmd.Prefix = prefixOfRawKeys;
|
confCmd.Prefix = prefixOfRawKeys;
|
||||||
}
|
}
|
||||||
|
if (partial) {
|
||||||
|
confCmd.MaxKeys = 10; // no need to list more!
|
||||||
|
}
|
||||||
|
|
||||||
const contents = [] as _Object[];
|
const contents = [] as _Object[];
|
||||||
const mtimeRecords: Record<string, number> = {};
|
const mtimeRecords: Record<string, number> = {};
|
||||||
const ctimeRecords: Record<string, number> = {};
|
const ctimeRecords: Record<string, number> = {};
|
||||||
|
const partsConcurrency = partial ? 1 : this.s3Config.partsConcurrency;
|
||||||
const queueHead = new PQueue({
|
const queueHead = new PQueue({
|
||||||
concurrency: this.s3Config.partsConcurrency,
|
concurrency: partsConcurrency,
|
||||||
autoStart: true,
|
autoStart: true,
|
||||||
});
|
});
|
||||||
queueHead.on("error", (error) => {
|
queueHead.on("error", (error) => {
|
||||||
@ -473,14 +484,20 @@ export class FakeFsS3 extends FakeFs {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
isTruncated = rsp.IsTruncated ?? false;
|
if (partial) {
|
||||||
confCmd.ContinuationToken = rsp.NextContinuationToken;
|
// do not loop over
|
||||||
if (
|
isTruncated = false;
|
||||||
isTruncated &&
|
} else {
|
||||||
(confCmd.ContinuationToken === undefined ||
|
// loop over
|
||||||
confCmd.ContinuationToken === "")
|
isTruncated = rsp.IsTruncated ?? false;
|
||||||
) {
|
confCmd.ContinuationToken = rsp.NextContinuationToken;
|
||||||
throw Error("isTruncated is true but no continuationToken provided");
|
if (
|
||||||
|
isTruncated &&
|
||||||
|
(confCmd.ContinuationToken === undefined ||
|
||||||
|
confCmd.ContinuationToken === "")
|
||||||
|
) {
|
||||||
|
throw Error("isTruncated is true but no continuationToken provided");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} while (isTruncated);
|
} while (isTruncated);
|
||||||
|
|
||||||
|
@ -357,6 +357,19 @@ export class FakeFsWebdav extends FakeFs {
|
|||||||
return contents.map((x) => fromWebdavItemToEntity(x, this.remoteBaseDir));
|
return contents.map((x) => fromWebdavItemToEntity(x, this.remoteBaseDir));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async walkPartial(): Promise<Entity[]> {
|
||||||
|
await this._init();
|
||||||
|
|
||||||
|
const contents = (await this.client.getDirectoryContents(
|
||||||
|
`/${this.remoteBaseDir}`,
|
||||||
|
{
|
||||||
|
deep: false, // partial, no need to recursive here
|
||||||
|
details: false /* no need for verbose details here */,
|
||||||
|
}
|
||||||
|
)) as FileStat[];
|
||||||
|
return contents.map((x) => fromWebdavItemToEntity(x, this.remoteBaseDir));
|
||||||
|
}
|
||||||
|
|
||||||
async stat(key: string): Promise<Entity> {
|
async stat(key: string): Promise<Entity> {
|
||||||
await this._init();
|
await this._init();
|
||||||
const fullPath = getWebdavPath(key, this.remoteBaseDir);
|
const fullPath = getWebdavPath(key, this.remoteBaseDir);
|
||||||
|
@ -133,6 +133,25 @@ export class FakeFsWebdis extends FakeFs {
|
|||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async walkPartial(): Promise<Entity[]> {
|
||||||
|
let cursor = "0";
|
||||||
|
const res: Entity[] = [];
|
||||||
|
const command = `SCAN/${cursor}/MATCH/rs:fs:v1:*:meta/COUNT/10`; // fewer keys
|
||||||
|
const rsp = (await (await this._fetchCommand("GET", command)).json())[
|
||||||
|
"SCAN"
|
||||||
|
];
|
||||||
|
// console.debug(rsp);
|
||||||
|
cursor = rsp[0];
|
||||||
|
for (const fullKeyWithMeta of rsp[1]) {
|
||||||
|
const realKey = getOrigPath(fullKeyWithMeta, this.remoteBaseDir);
|
||||||
|
res.push(await this.stat(realKey));
|
||||||
|
}
|
||||||
|
// no need to loop over cursor
|
||||||
|
// console.debug(`walk res:`);
|
||||||
|
// console.debug(res);
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
async stat(key: string): Promise<Entity> {
|
async stat(key: string): Promise<Entity> {
|
||||||
const fullKey = getWebdisPath(key, this.remoteBaseDir);
|
const fullKey = getWebdisPath(key, this.remoteBaseDir);
|
||||||
return await this._statFromRaw(fullKey);
|
return await this._statFromRaw(fullKey);
|
||||||
|
Loading…
Reference in New Issue
Block a user