check password using walkPartial instead of cache

This commit is contained in:
fyears 2024-05-18 01:33:20 +08:00
parent b584f89a95
commit 63c54d1956
9 changed files with 131 additions and 50 deletions

View File

@ -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(

View File

@ -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);

View File

@ -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()) {

View File

@ -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) {

View File

@ -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.");
} }

View File

@ -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));

View File

@ -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);

View File

@ -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);

View File

@ -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);