Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 79 additions & 0 deletions packages/filesystem/googledrive/googledrive.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,85 @@ describe("GoogleDriveFileSystem", () => {
expect(requestSpy).toHaveBeenCalledTimes(1);
});

it("writer should clear stale path cache and retry once on provider 404", async () => {
const fs = new GoogleDriveFileSystem("/", "token");
const notFoundError = new FileSystemError({
provider: "googledrive",
message: "Parent not found",
status: 404,
notFound: true,
});
const findFolderSpy = vi
.spyOn(fs, "findFolderByName")
.mockResolvedValueOnce({ id: "stale-base-id", name: "Base" })
.mockResolvedValueOnce({ id: "fresh-base-id", name: "Base" });

await fs.ensureDirExists("/Base");

const writer = await fs.create("Base/file.txt");
const findFileSpy = vi
.spyOn(fs, "findFileInDirectory")
.mockRejectedValueOnce(notFoundError)
.mockResolvedValueOnce(null);
const requestSpy = vi.spyOn(fs, "request").mockResolvedValue({});

await expect(writer.write("content")).resolves.toBeUndefined();

expect(findFolderSpy.mock.calls).toEqual([
["Base", "appDataFolder"],
["Base", "appDataFolder"],
]);
expect(findFileSpy.mock.calls).toEqual([
["file.txt", "stale-base-id"],
["file.txt", "fresh-base-id"],
]);
expect(requestSpy).toHaveBeenCalledTimes(1);
});

it("writer should not retry non-404 provider errors", async () => {
const fs = new GoogleDriveFileSystem("/", "token");
const conflictError = new FileSystemError({
provider: "googledrive",
message: "Conflict",
status: 409,
conflict: true,
});
const writer = await fs.create("Base/file.txt");
const ensureSpy = vi.spyOn(fs, "ensureDirExists").mockResolvedValue("base-id");
const findFileSpy = vi.spyOn(fs, "findFileInDirectory").mockRejectedValue(conflictError);

await expect(writer.write("content")).rejects.toBe(conflictError);

expect(ensureSpy).toHaveBeenCalledTimes(1);
expect(findFileSpy).toHaveBeenCalledTimes(1);
});

it("list should clear stale path cache and retry once on provider 404", async () => {
const fs = new GoogleDriveFileSystem("/Base", "token");
const notFoundError = new FileSystemError({
provider: "googledrive",
message: "Folder not found",
status: 404,
notFound: true,
});
const findFolderSpy = vi.spyOn(fs, "findFolderByName").mockResolvedValueOnce({ id: "stale-base-id", name: "Base" });

await fs.ensureDirExists("/Base");

const requestSpy = vi
.spyOn(fs, "request")
.mockRejectedValueOnce(notFoundError)
.mockResolvedValueOnce({ files: [{ id: "fresh-base-id", name: "Base" }] })
.mockResolvedValueOnce({ files: [] });

await expect(fs.list()).resolves.toEqual([]);

expect(findFolderSpy).toHaveBeenCalledTimes(1);
expect(String(requestSpy.mock.calls[0][0])).toContain("stale-base-id");
expect(String(requestSpy.mock.calls[1][0])).toContain("name%3D'Base'");
expect(String(requestSpy.mock.calls[2][0])).toContain("fresh-base-id");
});

it("request should return retry result after token refresh", async () => {
await localStorageDAO.saveValue("netdisk:token:googledrive", {
accessToken: "expired-token",
Expand Down
31 changes: 27 additions & 4 deletions packages/filesystem/googledrive/googledrive.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { AuthVerify } from "../auth";
import { FileSystemError } from "../error";
import { FileSystemError, isNotFoundError } from "../error";
import type FileSystem from "../filesystem";
import type { FileInfo, FileCreateOptions, FileReader, FileWriter } from "../filesystem";
import { joinPath } from "../utils";
Expand Down Expand Up @@ -284,6 +284,18 @@ export default class GoogleDriveFileSystem implements FileSystem {
return parentId;
}
async list(): Promise<FileInfo[]> {
try {
return await this.listWithResolvedFolder();
} catch (error) {
if (this.path === "/" || !isNotFoundError(error)) {
throw error;
}
this.clearPathCache();
return this.listWithResolvedFolder();
}
}

private async listWithResolvedFolder(): Promise<FileInfo[]> {
let folderId = "appDataFolder";

// 获取当前目录的ID
Expand Down Expand Up @@ -353,11 +365,22 @@ export default class GoogleDriveFileSystem implements FileSystem {
return null;
}

clearPathCache(path?: string): void {
if (!path) {
this.pathToIdCache.clear();
return;
}

const fullPath = joinPath(path);
const pathsToRemove = Array.from(this.pathToIdCache.keys()).filter(
(p) => p === fullPath || p.startsWith(`${fullPath}/`)
);
pathsToRemove.forEach((p) => this.pathToIdCache.delete(p));
}

// 清除相关缓存
clearRelatedCache(path: string): void {
// 清除路径缓存
const pathsToRemove = Array.from(this.pathToIdCache.keys()).filter((p) => p.startsWith(path));
pathsToRemove.forEach((p) => this.pathToIdCache.delete(p));
this.clearPathCache(path);
}

async getDirUrl(): Promise<string> {
Expand Down
13 changes: 13 additions & 0 deletions packages/filesystem/googledrive/rw.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { isNotFoundError } from "../error";
import type { FileInfo, FileReader, FileWriter } from "../filesystem";
import { joinPath } from "../utils";
import type GoogleDriveFileSystem from "./googledrive";
Expand Down Expand Up @@ -51,6 +52,18 @@ export class GoogleDriveFileWriter implements FileWriter {
}

async write(content: string | Blob): Promise<void> {
try {
return await this.writeWithResolvedParent(content);
} catch (error) {
if (!isNotFoundError(error)) {
throw error;
}
this.fs.clearPathCache();
return await this.writeWithResolvedParent(content);
}
}

private async writeWithResolvedParent(content: string | Blob): Promise<void> {
// 解析文件路径和文件名
const pathParts = this.path.split("/").filter(Boolean);
const fileName = pathParts.pop() || ""; // 获取文件名
Expand Down
Loading