From 8e69cd65d7e6a40c73d8904916027fdcdba093e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E4=B8=80=E4=B9=8B?= Date: Mon, 30 Jun 2025 12:01:46 +0800 Subject: [PATCH 1/7] wip: Google drive --- packages/filesystem/auth.ts | 2 +- packages/filesystem/factory.ts | 7 +- .../filesystem/googledrive/googledrive.ts | 252 ++++++++++++++++++ packages/filesystem/googledrive/rw.ts | 159 +++++++++++ 4 files changed, 418 insertions(+), 2 deletions(-) create mode 100644 packages/filesystem/googledrive/googledrive.ts create mode 100644 packages/filesystem/googledrive/rw.ts diff --git a/packages/filesystem/auth.ts b/packages/filesystem/auth.ts index a90cde8d2..2af922425 100644 --- a/packages/filesystem/auth.ts +++ b/packages/filesystem/auth.ts @@ -2,7 +2,7 @@ import { ExtServer, ExtServerApi } from "@App/app/const"; import { WarpTokenError } from "./error"; import { LocalStorageDAO } from "@App/app/repo/localStorage"; -type NetDiskType = "baidu" | "onedrive"; +type NetDiskType = "baidu" | "onedrive" | "googledrive"; export function GetNetDiskToken(netDiskType: NetDiskType): Promise<{ code: number; diff --git a/packages/filesystem/factory.ts b/packages/filesystem/factory.ts index 71e7c01de..e11b5e52d 100644 --- a/packages/filesystem/factory.ts +++ b/packages/filesystem/factory.ts @@ -1,12 +1,13 @@ import i18next from "i18next"; import BaiduFileSystem from "./baidu/baidu"; import FileSystem from "./filesystem"; +import GoogleDriveFileSystem from "./googledrive/googledrive"; import OneDriveFileSystem from "./onedrive/onedrive"; import WebDAVFileSystem from "./webdav/webdav"; import ZipFileSystem from "./zip/zip"; import i18n from "@App/locales/locales"; -export type FileSystemType = "zip" | "webdav" | "baidu-netdsik" | "onedrive"; +export type FileSystemType = "zip" | "webdav" | "baidu-netdsik" | "onedrive" | "googledrive"; export type FileSystemParams = { [key: string]: { @@ -37,6 +38,9 @@ export default class FileSystemFactory { case "onedrive": fs = new OneDriveFileSystem(); break; + case "googledrive": + fs = new GoogleDriveFileSystem(); + break; default: throw new Error("not found filesystem"); } @@ -57,6 +61,7 @@ export default class FileSystemFactory { }, "baidu-netdsik": {}, onedrive: {}, + googledrive: {}, }; } diff --git a/packages/filesystem/googledrive/googledrive.ts b/packages/filesystem/googledrive/googledrive.ts new file mode 100644 index 000000000..b61e152c0 --- /dev/null +++ b/packages/filesystem/googledrive/googledrive.ts @@ -0,0 +1,252 @@ +import { AuthVerify } from "../auth"; +import FileSystem, { File, FileReader, FileWriter } from "../filesystem"; +import { joinPath } from "../utils"; +import { GoogleDriveFileReader, GoogleDriveFileWriter } from "./rw"; + +export default class GoogleDriveFileSystem implements FileSystem { + accessToken?: string; + + path: string; + + constructor(path?: string, accessToken?: string) { + this.path = path || "/"; + this.accessToken = accessToken; + } + + async verify(): Promise { + const token = await AuthVerify("googledrive"); + this.accessToken = token; + return this.list().then(); + } + + open(file: File): Promise { + return Promise.resolve(new GoogleDriveFileReader(this, file)); + } + + openDir(path: string): Promise { + if (path.startsWith("ScriptCat")) { + path = path.substring(9); + } + return Promise.resolve(new GoogleDriveFileSystem(joinPath(this.path, path), this.accessToken)); + } + + create(path: string): Promise { + return Promise.resolve(new GoogleDriveFileWriter(this, joinPath(this.path, path))); + } + + async createDir(dir: string): Promise { + if (dir && dir.startsWith("ScriptCat")) { + dir = dir.substring(9); + if (dir.startsWith("/")) { + dir = dir.substring(1); + } + } + if (!dir) { + return Promise.resolve(); + } + const fullPath = joinPath(this.path, dir); + const dirs = fullPath.split("/").filter(Boolean); + + // 查找应用程序目录或创建根目录 + let parentId = "root"; + let currentPath = ""; + + // 检查ScriptCat应用目录是否存在 + const appFolderName = "ScriptCat"; + let appFolder = await this.findFolderByName(appFolderName, parentId); + if (!appFolder) { + appFolder = await this.createFolder(appFolderName, parentId); + } + parentId = appFolder.id; + currentPath = "/ScriptCat"; + + // 逐级创建目录 + for (const dirName of dirs) { + currentPath = joinPath(currentPath, dirName); + + // 查找目录是否已存在 + let folder = await this.findFolderByName(dirName, parentId); + if (!folder) { + // 不存在则创建 + folder = await this.createFolder(dirName, parentId); + } + parentId = folder.id; + } + + return Promise.resolve(); + } + + async findFolderByName(name: string, parentId: string): Promise<{ id: string; name: string } | null> { + const query = `name='${name}' and mimeType='application/vnd.google-apps.folder' and '${parentId}' in parents and trashed=false`; + const response = await this.request( + `https://www.googleapis.com/drive/v3/files?q=${encodeURIComponent(query)}&fields=files(id,name)` + ); + + if (response.files && response.files.length > 0) { + return response.files[0]; + } + return null; + } + + async createFolder(name: string, parentId: string): Promise<{ id: string; name: string }> { + const myHeaders = new Headers(); + myHeaders.append("Content-Type", "application/json"); + + const response = await this.request("https://www.googleapis.com/drive/v3/files", { + method: "POST", + headers: myHeaders, + body: JSON.stringify({ + name: name, + mimeType: "application/vnd.google-apps.folder", + parents: [parentId] + }), + }); + + if (response.error) { + throw new Error(JSON.stringify(response)); + } + + return { + id: response.id, + name: response.name + }; + } + + request(url: string, config?: RequestInit, nothen?: boolean) { + config = config || {}; + const headers = config.headers || new Headers(); + headers.append(`Authorization`, `Bearer ${this.accessToken}`); + config.headers = headers; + const ret = fetch(url, config); + if (nothen) { + return >ret; + } + return ret + .then((data) => data.json()) + .then(async (data) => { + if (data.error) { + if (data.error.code === 401) { + // Token可能过期,尝试刷新 + const token = await AuthVerify("googledrive", true); + this.accessToken = token; + headers.set(`Authorization`, `Bearer ${this.accessToken}`); + return fetch(url, config) + .then((retryData) => retryData.json()) + .then((retryData) => { + if (retryData.error) { + throw new Error(JSON.stringify(retryData)); + } + return retryData; + }); + } + throw new Error(JSON.stringify(data)); + } + return data; + }); + } + + async delete(path: string): Promise { + // 首先,找到要删除的文件或文件夹 + const fileId = await this.getFileId(joinPath(this.path, path)); + if (!fileId) { + throw new Error(`File or directory not found: ${path}`); + } + + // 删除文件或文件夹 + return this.request( + `https://www.googleapis.com/drive/v3/files/${fileId}`, + { + method: "DELETE", + }, + true + ).then(async (resp) => { + if (resp.status !== 204 && resp.status !== 200) { + throw new Error(await resp.text()); + } + }); + } + + async getFileId(path: string): Promise { + if (path === "/" || path === "") { + return "root"; + } + + // 从根目录开始逐级查找 + const pathParts = path.split("/").filter(Boolean); + let parentId = "root"; + + // 检查是否包含ScriptCat根目录 + if (pathParts[0] !== "ScriptCat") { + // 如果没有ScriptCat前缀,先查找ScriptCat目录 + const appFolder = await this.findFolderByName("ScriptCat", parentId); + if (!appFolder) { + return null; + } + parentId = appFolder.id; + } else { + // 如果有ScriptCat前缀,移除它并从ScriptCat目录开始查找 + const appFolder = await this.findFolderByName("ScriptCat", parentId); + if (!appFolder) { + return null; + } + parentId = appFolder.id; + pathParts.shift(); // 移除ScriptCat + } + + // 逐级查找路径 + let currentId = parentId; + for (const part of pathParts) { + const query = `name='${part}' and '${currentId}' in parents and trashed=false`; + const response = await this.request( + `https://www.googleapis.com/drive/v3/files?q=${encodeURIComponent(query)}&fields=files(id,name)` + ); + + if (!response.files || response.files.length === 0) { + return null; + } + + currentId = response.files[0].id; + } + + return currentId; + } + + async list(): Promise { + let folderId = "root"; + + // 获取当前目录的ID + if (this.path !== "/") { + const foundId = await this.getFileId(this.path); + if (!foundId) { + throw new Error(`Directory not found: ${this.path}`); + } + folderId = foundId; + } + + // 列出目录内容 + const query = `'${folderId}' in parents and trashed=false`; + const response = await this.request( + `https://www.googleapis.com/drive/v3/files?q=${encodeURIComponent(query)}&fields=files(id,name,mimeType,size,md5Checksum,createdTime,modifiedTime)` + ); + + const list: File[] = []; + if (response.files) { + for (const item of response.files) { + list.push({ + name: item.name, + path: this.path, + size: item.size ? parseInt(item.size, 10) : 0, + digest: item.md5Checksum || "", + createtime: new Date(item.createdTime).getTime(), + updatetime: new Date(item.modifiedTime).getTime(), + }); + } + } + + return list; + } + + getDirUrl(): Promise { + throw new Error("Method not implemented."); + } +} diff --git a/packages/filesystem/googledrive/rw.ts b/packages/filesystem/googledrive/rw.ts new file mode 100644 index 000000000..f149ad14d --- /dev/null +++ b/packages/filesystem/googledrive/rw.ts @@ -0,0 +1,159 @@ +import { calculateMd5 } from "@App/pkg/utils/utils"; +import { MD5 } from "crypto-js"; +import { File, FileReader, FileWriter } from "../filesystem"; +import { joinPath } from "../utils"; +import GoogleDriveFileSystem from "./googledrive"; + +export class GoogleDriveFileReader implements FileReader { + file: File; + + fs: GoogleDriveFileSystem; + + constructor(fs: GoogleDriveFileSystem, file: File) { + this.fs = fs; + this.file = file; + } + + async read(type?: "string" | "blob"): Promise { + // 首先获取文件ID + const fileId = await this.fs.getFileId(joinPath(this.file.path, this.file.name)); + if (!fileId) { + return Promise.reject(new Error(`File not found: ${this.file.name}`)); + } + + // 获取文件内容 + const data = await this.fs.request( + `https://www.googleapis.com/drive/v3/files/${fileId}?alt=media`, + {}, + true + ); + + if (data.status !== 200) { + return Promise.reject(await data.text()); + } + + switch (type) { + case "string": + return data.text(); + default: { + return data.blob(); + } + } + } +} + +export class GoogleDriveFileWriter implements FileWriter { + path: string; + + fs: GoogleDriveFileSystem; + + constructor(fs: GoogleDriveFileSystem, path: string) { + this.fs = fs; + this.path = path; + } + + size(content: string | Blob): number { + if (content instanceof Blob) { + return content.size; + } + return new Blob([content]).size; + } + + async md5(content: string | Blob): Promise { + if (content instanceof Blob) { + return calculateMd5(content); + } + return MD5(content).toString(); + } + + async write(content: string | Blob): Promise { + // 解析文件路径和文件名 + const pathParts = this.path.split('/').filter(Boolean); + const fileName = pathParts.pop() || ''; // 获取文件名 + const dirPath = '/' + pathParts.join('/'); // 重建目录路径 + + // 确保目录存在 + if (dirPath !== '/') { + await this.fs.createDir(dirPath); + } + + // 检查文件是否已存在 + const parentId = await this.getParentId(dirPath); + if (!parentId) { + throw new Error(`Directory not found: ${dirPath}`); + } + + const existingFileId = await this.findFile(fileName, parentId); + + if (existingFileId) { + // 如果文件存在,则更新 + return this.updateFile(existingFileId, content); + } else { + // 如果文件不存在,则创建 + return this.createNewFile(fileName, parentId, content); + } + } + + private async getParentId(dirPath: string): Promise { + return this.fs.getFileId(dirPath); + } + + private async findFile(fileName: string, parentId: string): Promise { + const query = `name='${fileName}' and '${parentId}' in parents and trashed=false`; + const response = await this.fs.request( + `https://www.googleapis.com/drive/v3/files?q=${encodeURIComponent(query)}&fields=files(id)` + ); + + if (response.files && response.files.length > 0) { + return response.files[0].id; + } + return null; + } + + private async updateFile(fileId: string, content: string | Blob): Promise { + const myHeaders = new Headers(); + // 不设置Content-Type,让浏览器自动处理multipart/form-data边界 + + const metadata = { + // 只更新内容,不更新元数据 + }; + + const formData = new FormData(); + formData.append('metadata', new Blob([JSON.stringify(metadata)], { type: 'application/json' })); + formData.append('file', content instanceof Blob ? content : new Blob([content])); + + await this.fs.request( + `https://www.googleapis.com/upload/drive/v3/files/${fileId}?uploadType=multipart`, + { + method: 'PATCH', + body: formData + } + ); + + return Promise.resolve(); + } + + private async createNewFile(fileName: string, parentId: string, content: string | Blob): Promise { + const myHeaders = new Headers(); + // 不设置Content-Type,让浏览器自动处理multipart/form-data边界 + + const metadata = { + name: fileName, + parents: [parentId] + }; + + const formData = new FormData(); + formData.append('metadata', new Blob([JSON.stringify(metadata)], { type: 'application/json' })); + formData.append('file', content instanceof Blob ? content : new Blob([content])); + + await this.fs.request( + `https://www.googleapis.com/upload/drive/v3/files?uploadType=multipart`, + { + method: 'POST', + body: formData + } + ); + + return Promise.resolve(); + } +} From 94b7d963c83ef7a579f66a9b22fa7cdee39baf5c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E4=B8=80=E4=B9=8B?= Date: Sat, 5 Jul 2025 16:23:11 +0800 Subject: [PATCH 2/7] wip: google drive --- src/pages/components/FileSystemParams/index.tsx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/pages/components/FileSystemParams/index.tsx b/src/pages/components/FileSystemParams/index.tsx index 308471d08..16c4049c9 100644 --- a/src/pages/components/FileSystemParams/index.tsx +++ b/src/pages/components/FileSystemParams/index.tsx @@ -37,6 +37,10 @@ const FileSystemParams: React.FC<{ key: "onedrive", name: "OneDrive", }, + { + key: "googledrive", + name: "Google Drive", + }, ]; return ( From 743c86afc0fe55251cb844ae2dcca4103d22cb71 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E4=B8=80=E4=B9=8B?= Date: Sat, 5 Jul 2025 18:04:01 +0800 Subject: [PATCH 3/7] =?UTF-8?q?=E2=9C=A8=20=E6=94=AF=E6=8C=81GoogleDrive?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../filesystem/googledrive/googledrive.ts | 185 +++++++++++------- packages/filesystem/googledrive/rw.ts | 108 ++++------ src/pages/options/routes/Tools.tsx | 4 +- 3 files changed, 155 insertions(+), 142 deletions(-) diff --git a/packages/filesystem/googledrive/googledrive.ts b/packages/filesystem/googledrive/googledrive.ts index b61e152c0..89ee0b150 100644 --- a/packages/filesystem/googledrive/googledrive.ts +++ b/packages/filesystem/googledrive/googledrive.ts @@ -8,6 +8,9 @@ export default class GoogleDriveFileSystem implements FileSystem { path: string; + // 缓存路径到文件ID的映射 + private pathToIdCache: Map = new Map(); + constructor(path?: string, accessToken?: string) { this.path = path || "/"; this.accessToken = accessToken; @@ -24,59 +27,48 @@ export default class GoogleDriveFileSystem implements FileSystem { } openDir(path: string): Promise { - if (path.startsWith("ScriptCat")) { - path = path.substring(9); - } return Promise.resolve(new GoogleDriveFileSystem(joinPath(this.path, path), this.accessToken)); } create(path: string): Promise { return Promise.resolve(new GoogleDriveFileWriter(this, joinPath(this.path, path))); - } - - async createDir(dir: string): Promise { - if (dir && dir.startsWith("ScriptCat")) { - dir = dir.substring(9); - if (dir.startsWith("/")) { - dir = dir.substring(1); - } - } + } async createDir(dir: string): Promise { if (!dir) { return Promise.resolve(); } + const fullPath = joinPath(this.path, dir); const dirs = fullPath.split("/").filter(Boolean); - - // 查找应用程序目录或创建根目录 + + // 从根目录开始逐级创建目录 let parentId = "root"; let currentPath = ""; - - // 检查ScriptCat应用目录是否存在 - const appFolderName = "ScriptCat"; - let appFolder = await this.findFolderByName(appFolderName, parentId); - if (!appFolder) { - appFolder = await this.createFolder(appFolderName, parentId); - } - parentId = appFolder.id; - currentPath = "/ScriptCat"; - // 逐级创建目录 + // 逐级创建目录,使用缓存减少重复请求 for (const dirName of dirs) { currentPath = joinPath(currentPath, dirName); - // 查找目录是否已存在 - let folder = await this.findFolderByName(dirName, parentId); - if (!folder) { - // 不存在则创建 - folder = await this.createFolder(dirName, parentId); + // 先检查缓存 + let folderId = this.pathToIdCache.get(currentPath); + + if (!folderId) { + // 缓存中没有,查找目录是否已存在 + let folder = await this.findFolderByName(dirName, parentId); + if (!folder) { + // 不存在则创建 + folder = await this.createFolder(dirName, parentId); + } + folderId = folder.id; + + // 更新缓存 + this.pathToIdCache.set(currentPath, folderId); } - parentId = folder.id; + + parentId = folderId; } return Promise.resolve(); - } - - async findFolderByName(name: string, parentId: string): Promise<{ id: string; name: string } | null> { + } async findFolderByName(name: string, parentId: string): Promise<{ id: string; name: string } | null> { const query = `name='${name}' and mimeType='application/vnd.google-apps.folder' and '${parentId}' in parents and trashed=false`; const response = await this.request( `https://www.googleapis.com/drive/v3/files?q=${encodeURIComponent(query)}&fields=files(id,name)` @@ -91,24 +83,24 @@ export default class GoogleDriveFileSystem implements FileSystem { async createFolder(name: string, parentId: string): Promise<{ id: string; name: string }> { const myHeaders = new Headers(); myHeaders.append("Content-Type", "application/json"); - + const response = await this.request("https://www.googleapis.com/drive/v3/files", { method: "POST", headers: myHeaders, body: JSON.stringify({ name: name, mimeType: "application/vnd.google-apps.folder", - parents: [parentId] + parents: [parentId], }), }); - + if (response.error) { throw new Error(JSON.stringify(response)); } - + return { id: response.id, - name: response.name + name: response.name, }; } @@ -143,17 +135,17 @@ export default class GoogleDriveFileSystem implements FileSystem { } return data; }); - } - - async delete(path: string): Promise { + } async delete(path: string): Promise { + const fullPath = joinPath(this.path, path); + // 首先,找到要删除的文件或文件夹 - const fileId = await this.getFileId(joinPath(this.path, path)); + const fileId = await this.getFileId(fullPath); if (!fileId) { throw new Error(`File or directory not found: ${path}`); } // 删除文件或文件夹 - return this.request( + await this.request( `https://www.googleapis.com/drive/v3/files/${fileId}`, { method: "DELETE", @@ -164,39 +156,37 @@ export default class GoogleDriveFileSystem implements FileSystem { throw new Error(await resp.text()); } }); - } - - async getFileId(path: string): Promise { + + // 清除相关缓存 + this.clearRelatedCache(fullPath); + }async getFileId(path: string): Promise { if (path === "/" || path === "") { return "root"; } + // 先检查缓存 + const cachedId = this.pathToIdCache.get(path); + if (cachedId) { + return cachedId; + } + // 从根目录开始逐级查找 const pathParts = path.split("/").filter(Boolean); let parentId = "root"; - - // 检查是否包含ScriptCat根目录 - if (pathParts[0] !== "ScriptCat") { - // 如果没有ScriptCat前缀,先查找ScriptCat目录 - const appFolder = await this.findFolderByName("ScriptCat", parentId); - if (!appFolder) { - return null; - } - parentId = appFolder.id; - } else { - // 如果有ScriptCat前缀,移除它并从ScriptCat目录开始查找 - const appFolder = await this.findFolderByName("ScriptCat", parentId); - if (!appFolder) { - return null; - } - parentId = appFolder.id; - pathParts.shift(); // 移除ScriptCat - } + let currentPath = ""; // 逐级查找路径 - let currentId = parentId; for (const part of pathParts) { - const query = `name='${part}' and '${currentId}' in parents and trashed=false`; + currentPath = joinPath(currentPath, part); + + // 检查这个路径是否已经缓存 + const cachedPartId = this.pathToIdCache.get(currentPath); + if (cachedPartId) { + parentId = cachedPartId; + continue; + } + + const query = `name='${part}' and '${parentId}' in parents and trashed=false`; const response = await this.request( `https://www.googleapis.com/drive/v3/files?q=${encodeURIComponent(query)}&fields=files(id,name)` ); @@ -205,13 +195,14 @@ export default class GoogleDriveFileSystem implements FileSystem { return null; } - currentId = response.files[0].id; + parentId = response.files[0].id; + + // 缓存这个路径的ID + this.pathToIdCache.set(currentPath, parentId); } - return currentId; - } - - async list(): Promise { + return parentId; + } async list(): Promise { let folderId = "root"; // 获取当前目录的ID @@ -246,7 +237,59 @@ export default class GoogleDriveFileSystem implements FileSystem { return list; } + // 辅助方法:在指定目录中查找文件 + async findFileInDirectory(fileName: string, parentId: string): Promise { + const query = `name='${fileName}' and '${parentId}' in parents and trashed=false and mimeType!='application/vnd.google-apps.folder'`; + const response = await this.request( + `https://www.googleapis.com/drive/v3/files?q=${encodeURIComponent(query)}&fields=files(id)` + ); + + if (response.files && response.files.length > 0) { + return response.files[0].id; + } + return null; + } + + // 清除相关缓存 + clearRelatedCache(path: string): void { + // 清除路径缓存 + const pathsToRemove = Array.from(this.pathToIdCache.keys()).filter(p => p.startsWith(path)); + pathsToRemove.forEach(p => this.pathToIdCache.delete(p)); + } + getDirUrl(): Promise { throw new Error("Method not implemented."); } + + // 确保目录存在并返回目录ID,优化Writer避免重复获取 + async ensureDirExists(dirPath: string): Promise { + if (dirPath === "/" || dirPath === "") { + return "root"; + } + + // 先检查缓存 + const cachedId = this.pathToIdCache.get(dirPath); + if (cachedId) { + return cachedId; + } + + // 缓存中没有,先尝试创建目录 + await this.createDir(dirPath); + + // 再次检查缓存,应该已经被设置了 + const id = this.pathToIdCache.get(dirPath); + if (id) { + return id; + } + + // 如果还是没有,使用getFileId方法 + const foundId = await this.getFileId(dirPath); + if (!foundId) { + throw new Error(`Failed to create or find directory: ${dirPath}`); + } + + // 缓存结果 + this.pathToIdCache.set(dirPath, foundId); + return foundId; + } } diff --git a/packages/filesystem/googledrive/rw.ts b/packages/filesystem/googledrive/rw.ts index f149ad14d..26b25446b 100644 --- a/packages/filesystem/googledrive/rw.ts +++ b/packages/filesystem/googledrive/rw.ts @@ -22,16 +22,12 @@ export class GoogleDriveFileReader implements FileReader { } // 获取文件内容 - const data = await this.fs.request( - `https://www.googleapis.com/drive/v3/files/${fileId}?alt=media`, - {}, - true - ); - + const data = await this.fs.request(`https://www.googleapis.com/drive/v3/files/${fileId}?alt=media`, {}, true); + if (data.status !== 200) { return Promise.reject(await data.text()); } - + switch (type) { case "string": return data.text(); @@ -68,23 +64,16 @@ export class GoogleDriveFileWriter implements FileWriter { async write(content: string | Blob): Promise { // 解析文件路径和文件名 - const pathParts = this.path.split('/').filter(Boolean); - const fileName = pathParts.pop() || ''; // 获取文件名 - const dirPath = '/' + pathParts.join('/'); // 重建目录路径 - - // 确保目录存在 - if (dirPath !== '/') { - await this.fs.createDir(dirPath); - } - - // 检查文件是否已存在 - const parentId = await this.getParentId(dirPath); - if (!parentId) { - throw new Error(`Directory not found: ${dirPath}`); - } - - const existingFileId = await this.findFile(fileName, parentId); - + const pathParts = this.path.split("/").filter(Boolean); + const fileName = pathParts.pop() || ""; // 获取文件名 + const dirPath = "/" + pathParts.join("/"); // 重建目录路径 + + // 使用优化的方法确保目录存在并获取ID + const parentId = await this.fs.ensureDirExists(dirPath); + + // 使用优化的查找方法 + const existingFileId = await this.fs.findFileInDirectory(fileName, parentId); + if (existingFileId) { // 如果文件存在,则更新 return this.updateFile(existingFileId, content); @@ -92,68 +81,49 @@ export class GoogleDriveFileWriter implements FileWriter { // 如果文件不存在,则创建 return this.createNewFile(fileName, parentId, content); } + } private async findFile(fileName: string, parentId: string): Promise { + // 使用文件系统的优化查找方法 + return this.fs.findFileInDirectory(fileName, parentId); } - - private async getParentId(dirPath: string): Promise { - return this.fs.getFileId(dirPath); - } - - private async findFile(fileName: string, parentId: string): Promise { - const query = `name='${fileName}' and '${parentId}' in parents and trashed=false`; - const response = await this.fs.request( - `https://www.googleapis.com/drive/v3/files?q=${encodeURIComponent(query)}&fields=files(id)` - ); - - if (response.files && response.files.length > 0) { - return response.files[0].id; - } - return null; - } - + private async updateFile(fileId: string, content: string | Blob): Promise { const myHeaders = new Headers(); // 不设置Content-Type,让浏览器自动处理multipart/form-data边界 - + const metadata = { // 只更新内容,不更新元数据 }; - + const formData = new FormData(); - formData.append('metadata', new Blob([JSON.stringify(metadata)], { type: 'application/json' })); - formData.append('file', content instanceof Blob ? content : new Blob([content])); - - await this.fs.request( - `https://www.googleapis.com/upload/drive/v3/files/${fileId}?uploadType=multipart`, - { - method: 'PATCH', - body: formData - } - ); - + formData.append("metadata", new Blob([JSON.stringify(metadata)], { type: "application/json" })); + formData.append("file", content instanceof Blob ? content : new Blob([content])); + + await this.fs.request(`https://www.googleapis.com/upload/drive/v3/files/${fileId}?uploadType=multipart`, { + method: "PATCH", + body: formData, + }); + return Promise.resolve(); } - + private async createNewFile(fileName: string, parentId: string, content: string | Blob): Promise { const myHeaders = new Headers(); // 不设置Content-Type,让浏览器自动处理multipart/form-data边界 - + const metadata = { name: fileName, - parents: [parentId] + parents: [parentId], }; - + const formData = new FormData(); - formData.append('metadata', new Blob([JSON.stringify(metadata)], { type: 'application/json' })); - formData.append('file', content instanceof Blob ? content : new Blob([content])); - - await this.fs.request( - `https://www.googleapis.com/upload/drive/v3/files?uploadType=multipart`, - { - method: 'POST', - body: formData - } - ); - + formData.append("metadata", new Blob([JSON.stringify(metadata)], { type: "application/json" })); + formData.append("file", content instanceof Blob ? content : new Blob([content])); + + await this.fs.request(`https://www.googleapis.com/upload/drive/v3/files?uploadType=multipart`, { + method: "POST", + body: formData, + }); + return Promise.resolve(); } } diff --git a/src/pages/options/routes/Tools.tsx b/src/pages/options/routes/Tools.tsx index 9071a7a6f..255cc12fa 100644 --- a/src/pages/options/routes/Tools.tsx +++ b/src/pages/options/routes/Tools.tsx @@ -162,9 +162,9 @@ function Tools() { list = list.filter((file) => file.name.endsWith(".zip")); if (list.length === 0) { Message.info(t("no_backup_files")!); - return; + } else { + setBackupFileList(list); } - setBackupFileList(list); } catch (e) { Message.error(`${t("get_backup_files_failed")}: ${e}`); } From cd101475e7e6e527778a6841ac42775d65c4c0bd Mon Sep 17 00:00:00 2001 From: wangyizhi Date: Sat, 5 Jul 2025 18:07:05 +0800 Subject: [PATCH 4/7] Update packages/filesystem/googledrive/rw.ts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- packages/filesystem/googledrive/rw.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/filesystem/googledrive/rw.ts b/packages/filesystem/googledrive/rw.ts index 26b25446b..76ba91ed1 100644 --- a/packages/filesystem/googledrive/rw.ts +++ b/packages/filesystem/googledrive/rw.ts @@ -87,7 +87,6 @@ export class GoogleDriveFileWriter implements FileWriter { } private async updateFile(fileId: string, content: string | Blob): Promise { - const myHeaders = new Headers(); // 不设置Content-Type,让浏览器自动处理multipart/form-data边界 const metadata = { From e5f44529222e614654c8c43bc17577d0df18b35e Mon Sep 17 00:00:00 2001 From: wangyizhi Date: Sat, 5 Jul 2025 18:11:04 +0800 Subject: [PATCH 5/7] Apply suggestions from code review Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- packages/filesystem/googledrive/googledrive.ts | 11 +++++++++-- packages/filesystem/googledrive/rw.ts | 1 - 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/packages/filesystem/googledrive/googledrive.ts b/packages/filesystem/googledrive/googledrive.ts index 89ee0b150..3b09c80a0 100644 --- a/packages/filesystem/googledrive/googledrive.ts +++ b/packages/filesystem/googledrive/googledrive.ts @@ -257,8 +257,15 @@ export default class GoogleDriveFileSystem implements FileSystem { pathsToRemove.forEach(p => this.pathToIdCache.delete(p)); } - getDirUrl(): Promise { - throw new Error("Method not implemented."); + async getDirUrl(): Promise { + // Retrieve the folder ID for the current path + const folderId = await this.getFileId(this.path); + if (!folderId) { + throw new Error(`Directory not found: ${this.path}`); + } + + // Construct and return the Google Drive folder URL + return `https://drive.google.com/drive/folders/${folderId}`; } // 确保目录存在并返回目录ID,优化Writer避免重复获取 diff --git a/packages/filesystem/googledrive/rw.ts b/packages/filesystem/googledrive/rw.ts index 76ba91ed1..5970c7cc6 100644 --- a/packages/filesystem/googledrive/rw.ts +++ b/packages/filesystem/googledrive/rw.ts @@ -106,7 +106,6 @@ export class GoogleDriveFileWriter implements FileWriter { } private async createNewFile(fileName: string, parentId: string, content: string | Blob): Promise { - const myHeaders = new Headers(); // 不设置Content-Type,让浏览器自动处理multipart/form-data边界 const metadata = { From ae67ede0f02bdc5e19cc7fb1c73ba14495348a48 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E4=B8=80=E4=B9=8B?= Date: Sat, 5 Jul 2025 18:14:18 +0800 Subject: [PATCH 6/7] refactor: remove unused methods from GoogleDriveFileWriter - Remove unused size() and md5() methods - Remove unused findFile() private method - Remove unused imports (calculateMd5, MD5) --- packages/filesystem/googledrive/rw.ts | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/packages/filesystem/googledrive/rw.ts b/packages/filesystem/googledrive/rw.ts index 5970c7cc6..89a83ba4f 100644 --- a/packages/filesystem/googledrive/rw.ts +++ b/packages/filesystem/googledrive/rw.ts @@ -1,5 +1,3 @@ -import { calculateMd5 } from "@App/pkg/utils/utils"; -import { MD5 } from "crypto-js"; import { File, FileReader, FileWriter } from "../filesystem"; import { joinPath } from "../utils"; import GoogleDriveFileSystem from "./googledrive"; @@ -48,20 +46,6 @@ export class GoogleDriveFileWriter implements FileWriter { this.path = path; } - size(content: string | Blob): number { - if (content instanceof Blob) { - return content.size; - } - return new Blob([content]).size; - } - - async md5(content: string | Blob): Promise { - if (content instanceof Blob) { - return calculateMd5(content); - } - return MD5(content).toString(); - } - async write(content: string | Blob): Promise { // 解析文件路径和文件名 const pathParts = this.path.split("/").filter(Boolean); @@ -81,9 +65,6 @@ export class GoogleDriveFileWriter implements FileWriter { // 如果文件不存在,则创建 return this.createNewFile(fileName, parentId, content); } - } private async findFile(fileName: string, parentId: string): Promise { - // 使用文件系统的优化查找方法 - return this.fs.findFileInDirectory(fileName, parentId); } private async updateFile(fileId: string, content: string | Blob): Promise { From 0f00704651af6de37f90456895feb9c77424f56c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E4=B8=80=E4=B9=8B?= Date: Sat, 5 Jul 2025 19:29:04 +0800 Subject: [PATCH 7/7] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E9=87=8D=E5=A4=8D?= =?UTF-8?q?=E5=88=9B=E5=BB=BA=E7=9B=AE=E5=BD=95=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/filesystem/googledrive/googledrive.ts | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/packages/filesystem/googledrive/googledrive.ts b/packages/filesystem/googledrive/googledrive.ts index 3b09c80a0..cca725a1d 100644 --- a/packages/filesystem/googledrive/googledrive.ts +++ b/packages/filesystem/googledrive/googledrive.ts @@ -280,16 +280,7 @@ export default class GoogleDriveFileSystem implements FileSystem { return cachedId; } - // 缓存中没有,先尝试创建目录 - await this.createDir(dirPath); - - // 再次检查缓存,应该已经被设置了 - const id = this.pathToIdCache.get(dirPath); - if (id) { - return id; - } - - // 如果还是没有,使用getFileId方法 + // 如果没有缓存,使用getFileId方法 const foundId = await this.getFileId(dirPath); if (!foundId) { throw new Error(`Failed to create or find directory: ${dirPath}`);