Skip to content

Commit 6b5ed41

Browse files
committed
Merge branch 'main' into release/v1.3
2 parents 3d76efb + 5d70786 commit 6b5ed41

File tree

8 files changed

+182
-58
lines changed

8 files changed

+182
-58
lines changed

packages/filesystem/baidu/baidu.ts

Lines changed: 37 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -126,19 +126,38 @@ export default class BaiduFileSystem implements FileSystem {
126126
});
127127
}
128128

129-
list(): Promise<File[]> {
130-
return this.request(
131-
`https://pan.baidu.com/rest/2.0/xpan/file?method=list&dir=${encodeURIComponent(
132-
this.path
133-
)}&order=time&access_token=${this.accessToken}`
134-
).then((data) => {
129+
async list(): Promise<File[]> {
130+
const list: File[] = [];
131+
let start = 0;
132+
const limit = 200;
133+
// 防御性:限制最大分页轮询次数,避免在 API 异常返回时出现无限循环
134+
const MAX_ITERATIONS = 100;
135+
let iterations = 0;
136+
137+
while (true) {
138+
if (iterations >= MAX_ITERATIONS) {
139+
throw new Error(
140+
"BaiduFileSystem.list: exceeded max pagination iterations, possible infinite loop from Baidu API response"
141+
);
142+
}
143+
iterations += 1;
144+
const data = await this.request(
145+
`https://pan.baidu.com/rest/2.0/xpan/file?method=list&dir=${encodeURIComponent(
146+
this.path
147+
)}&order=time&start=${start}&limit=${limit}&access_token=${this.accessToken}`
148+
);
149+
135150
if (data.errno) {
136151
if (data.errno === -9) {
137-
return [];
152+
break;
138153
}
139154
throw new Error(JSON.stringify(data));
140155
}
141-
const list: File[] = [];
156+
157+
if (!data.list || data.list.length === 0) {
158+
break;
159+
}
160+
142161
data.list.forEach((val: any) => {
143162
list.push({
144163
fsid: val.fs_id,
@@ -150,8 +169,16 @@ export default class BaiduFileSystem implements FileSystem {
150169
updatetime: val.server_mtime * 1000,
151170
});
152171
});
153-
return list;
154-
});
172+
173+
// 如果返回的数据少于limit,说明已经是最后一页
174+
if (data.list.length < limit) {
175+
break;
176+
}
177+
178+
start += limit;
179+
}
180+
181+
return list;
155182
}
156183

157184
async getDirUrl(): Promise<string> {

packages/filesystem/dropbox/dropbox.ts

Lines changed: 38 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -149,34 +149,58 @@ export default class DropboxFileSystem implements FileSystem {
149149
const myHeaders = new Headers();
150150
myHeaders.append("Content-Type", "application/json");
151151

152-
const response = await this.request("https://api.dropboxapi.com/2/files/list_folder", {
152+
let response = await this.request("https://api.dropboxapi.com/2/files/list_folder", {
153153
method: "POST",
154154
headers: myHeaders,
155155
body: JSON.stringify({
156156
path: folderPath,
157157
}),
158158
}).catch((e) => {
159159
if (e.message.includes("path/not_found")) {
160-
return { entries: [] }; // 返回空数组以避免后续错误
160+
return { entries: [], has_more: false }; // 返回空数组以避免后续错误
161161
}
162162
throw e;
163163
});
164164

165165
const list: File[] = [];
166-
if (response.entries) {
167-
for (const item of response.entries) {
168-
// 只包含文件,跳过文件夹
169-
if (item[".tag"] === "file") {
170-
list.push({
171-
name: item.name,
172-
path: this.path,
173-
size: item.size || 0,
174-
digest: item.content_hash || "",
175-
createtime: new Date(item.client_modified).getTime(),
176-
updatetime: new Date(item.server_modified).getTime(),
177-
});
166+
167+
const MAX_ITERATIONS = 100;
168+
let iterationCount = 0;
169+
170+
while (true) {
171+
iterationCount++;
172+
if (iterationCount > MAX_ITERATIONS) {
173+
throw new Error("Dropbox list pagination exceeded maximum iterations");
174+
}
175+
if (response.entries) {
176+
for (const item of response.entries) {
177+
// 只包含文件,跳过文件夹
178+
if (item[".tag"] === "file") {
179+
list.push({
180+
name: item.name,
181+
path: this.path,
182+
size: item.size || 0,
183+
digest: item.content_hash || "",
184+
createtime: new Date(item.client_modified).getTime(),
185+
updatetime: new Date(item.server_modified).getTime(),
186+
});
187+
}
178188
}
179189
}
190+
191+
// 检查是否有更多数据
192+
if (!response.has_more) {
193+
break;
194+
}
195+
196+
// 获取下一页数据
197+
response = await this.request("https://api.dropboxapi.com/2/files/list_folder/continue", {
198+
method: "POST",
199+
headers: myHeaders,
200+
body: JSON.stringify({
201+
cursor: response.cursor,
202+
}),
203+
});
180204
}
181205

182206
return list;

packages/filesystem/googledrive/googledrive.ts

Lines changed: 37 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -220,23 +220,45 @@ export default class GoogleDriveFileSystem implements FileSystem {
220220
folderId = foundId;
221221
}
222222

223-
// 列出目录内容
223+
// 列出目录内容,处理分页
224+
const list: File[] = [];
225+
let pageToken: string | undefined = undefined;
226+
224227
const query = `'${folderId}' in parents and trashed=false`;
225-
const response = await this.request(
226-
`https://www.googleapis.com/drive/v3/files?q=${encodeURIComponent(query)}&fields=files(id,name,mimeType,size,md5Checksum,createdTime,modifiedTime)&spaces=appDataFolder`
227-
);
228+
const MAX_ITERATIONS = 100;
229+
let iterations = 0;
228230

229-
const list: File[] = [];
230-
if (response.files) {
231-
for (const item of response.files) {
232-
list.push({
233-
name: item.name,
234-
path: this.path,
235-
size: item.size ? parseInt(item.size, 10) : 0,
236-
digest: item.md5Checksum || "",
237-
createtime: new Date(item.createdTime).getTime(),
238-
updatetime: new Date(item.modifiedTime).getTime(),
239-
});
231+
while (true) {
232+
iterations += 1;
233+
if (iterations > MAX_ITERATIONS) {
234+
throw new Error("GoogleDrive list pagination exceeded maximum iterations");
235+
}
236+
const url = new URL("https://www.googleapis.com/drive/v3/files");
237+
url.searchParams.set("q", query);
238+
url.searchParams.set("fields", "files(id,name,mimeType,size,md5Checksum,createdTime,modifiedTime),nextPageToken");
239+
url.searchParams.set("spaces", "appDataFolder");
240+
if (pageToken) {
241+
url.searchParams.set("pageToken", pageToken);
242+
}
243+
244+
const response = await this.request(url.toString());
245+
246+
if (response.files) {
247+
for (const item of response.files) {
248+
list.push({
249+
name: item.name,
250+
path: this.path,
251+
size: item.size ? parseInt(item.size, 10) : 0,
252+
digest: item.md5Checksum || "",
253+
createtime: new Date(item.createdTime).getTime(),
254+
updatetime: new Date(item.modifiedTime).getTime(),
255+
});
256+
}
257+
}
258+
259+
pageToken = response.nextPageToken;
260+
if (!pageToken) {
261+
break;
240262
}
241263
}
242264

packages/filesystem/limiter.ts

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ export class RateLimiter {
3131

3232
this.running++;
3333
try {
34-
return await fn();
34+
return await this.executeWithRetry(fn);
3535
} finally {
3636
this.running--;
3737
// 执行完成后,从队列中取出下一个等待的操作
@@ -41,6 +41,33 @@ export class RateLimiter {
4141
}
4242
}
4343
}
44+
45+
/**
46+
* 执行操作并处理 429 错误重试
47+
* @param fn 要执行的操作函数
48+
* @returns 操作结果
49+
*/
50+
private async executeWithRetry<T>(fn: () => Promise<T>): Promise<T> {
51+
// 最多重试 10 次
52+
for (let i = 0; i <= 10; i++) {
53+
try {
54+
return await fn();
55+
} catch (error) {
56+
// 检查错误字符串中是否包含 429
57+
const errorStr = String(error);
58+
if (errorStr.includes("429") && i < 10) {
59+
// 遇到 429 错误且未达到重试上限,采用指数退避策略延迟后继续重试
60+
const delay = Math.min(2000 * Math.pow(2, i), 60000);
61+
await new Promise((resolve) => setTimeout(resolve, delay));
62+
// 继续下一次循环重试
63+
continue;
64+
}
65+
// 其他错误或达到重试上限,直接抛出
66+
throw error;
67+
}
68+
}
69+
throw new Error("Max retries exceeded");
70+
}
4471
}
4572

4673
// 文件系统限速器,防止并发请求过多达到服务器限制

packages/filesystem/onedrive/onedrive.ts

Lines changed: 29 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -125,17 +125,35 @@ export default class OneDriveFileSystem implements FileSystem {
125125
} else {
126126
path = `:${path}:`;
127127
}
128-
const data = await this.request(`https://graph.microsoft.com/v1.0/me/drive/special/approot${path}/children`);
129-
const list: File[] = data.value.map((val: any) => {
130-
return {
131-
name: val.name,
132-
path: this.path,
133-
size: val.size,
134-
digest: val.eTag,
135-
createtime: new Date(val.createdDateTime).getTime(),
136-
updatetime: new Date(val.lastModifiedDateTime).getTime(),
137-
};
138-
});
128+
129+
const list: File[] = [];
130+
let nextLink: string | undefined = `https://graph.microsoft.com/v1.0/me/drive/special/approot${path}/children`;
131+
let iterationCount = 0;
132+
const MAX_ITERATIONS = 100;
133+
134+
while (nextLink) {
135+
iterationCount += 1;
136+
if (iterationCount > MAX_ITERATIONS) {
137+
throw new Error("OneDrive list pagination exceeded maximum iterations");
138+
}
139+
const data = await this.request(nextLink);
140+
141+
if (data.value) {
142+
for (const val of data.value) {
143+
list.push({
144+
name: val.name,
145+
path: this.path,
146+
size: val.size,
147+
digest: val.eTag,
148+
createtime: new Date(val.createdDateTime).getTime(),
149+
updatetime: new Date(val.lastModifiedDateTime).getTime(),
150+
});
151+
}
152+
}
153+
154+
nextLink = data["@odata.nextLink"];
155+
}
156+
139157
return list;
140158
}
141159

src/app/service/service_worker/script.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -434,12 +434,13 @@ export class ScriptService {
434434
}
435435

436436
async deleteScript(uuid: string) {
437-
const logger = this.logger.with({ uuid });
437+
let logger = this.logger.with({ uuid });
438438
const script = await this.scriptDAO.get(uuid);
439439
if (!script) {
440440
logger.error("script not found");
441441
throw new Error("script not found");
442442
}
443+
logger = logger.with({ name: script.name });
443444
const storageName = getStorageName(script);
444445
return this.scriptDAO
445446
.delete(uuid)

src/app/service/service_worker/synchronize.ts

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -420,8 +420,9 @@ export class SynchronizeService {
420420
}
421421
const updatetime = script.updatetime || script.createtime;
422422
// 对比脚本更新时间和文件更新时间
423-
if (updatetime > file.script!.updatetime) {
424-
// 如果脚本更新时间大于文件更新时间,则上传文件
423+
if (updatetime > file.script!.updatetime || !file.meta) {
424+
// 如果脚本更新时间大于文件更新时间
425+
// 或者不存在.meta文件,则上传文件
425426
result.push(this.pushScript(fs, script));
426427
} else {
427428
// 如果脚本更新时间小于文件更新时间,则更新脚本
@@ -433,6 +434,11 @@ export class SynchronizeService {
433434
}
434435
// 如果脚本不存在,且文件存在,则安装脚本
435436
if (file.script) {
437+
if (!file.meta) {
438+
// 如果.meta文件不存在,则删除脚本文件,并跳过
439+
result.push(fs.delete(file.script.name));
440+
return;
441+
}
436442
updateScript.set(uuid, true);
437443
result.push(this.pullScript(fs, file as SyncFiles, cloudStatus[uuid]));
438444
}
@@ -537,7 +543,6 @@ export class SynchronizeService {
537543
);
538544
} else {
539545
// 直接删除所有相关文件
540-
await fs.delete(filename);
541546
await fs.delete(`${uuid}.meta.json`);
542547
}
543548
logger.info("delete success");

src/pkg/config/config.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -274,7 +274,7 @@ export class SystemConfig {
274274
return this._get<CATFileStorage>("cat_file_storage", this.defaultCatFileStorage());
275275
}
276276

277-
setCatFileStorage(data: CATFileStorage | undefined) {
277+
setCatFileStorage(data: CATFileStorage) {
278278
this._set("cat_file_storage", data);
279279
}
280280

@@ -292,7 +292,7 @@ export class SystemConfig {
292292

293293
setEslintConfig(v: string) {
294294
if (v === "") {
295-
this._set("eslint_config", undefined);
295+
this._set("eslint_config", defaultConfig);
296296
return;
297297
}
298298
JSON.parse(v);
@@ -305,7 +305,7 @@ export class SystemConfig {
305305

306306
setEditorConfig(v: string) {
307307
if (v === "") {
308-
this._set("editor_config", undefined);
308+
this._set("editor_config", editorDefaultConfig);
309309
return;
310310
}
311311
JSON.parse(v);

0 commit comments

Comments
 (0)