Skip to content

Commit 7308de0

Browse files
authored
fix(#927): 修复无法正常上传记录、标签和配置的问题 (#932)
1 parent a10965d commit 7308de0

File tree

4 files changed

+235
-88
lines changed

4 files changed

+235
-88
lines changed

src/components/title-bar-toolbars/sync-toggle.tsx

Lines changed: 212 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
"use client"
22

3+
/* eslint-disable @typescript-eslint/no-explicit-any, @typescript-eslint/no-unused-vars */
4+
35
import * as React from "react"
46
import { DownloadCloud, Loader2, UploadCloud, CloudSync } from "lucide-react"
57
import { useTranslations } from 'next-intl'
@@ -19,14 +21,154 @@ import useTagStore from "@/stores/tag"
1921
import useChatStore from "@/stores/chat"
2022
import useSettingStore from "@/stores/setting"
2123
import { Store } from "@tauri-apps/plugin-store"
22-
import { uint8ArrayToBase64, uploadFile as uploadGithubFile, getFiles as githubGetFiles, decodeBase64ToString } from "@/lib/sync/github"
23-
import { getFiles as giteeGetFiles, uploadFile as uploadGiteeFile } from "@/lib/sync/gitee"
24-
import { uploadFile as uploadGitlabFile, getFiles as gitlabGetFiles, getFileContent as gitlabGetFileContent } from "@/lib/sync/gitlab"
25-
import { uploadFile as uploadGiteaFile, getFiles as giteaGetFiles, getFileContent as giteaGetFileContent } from "@/lib/sync/gitea"
24+
import { uint8ArrayToBase64, decodeBase64ToString } from "@/lib/sync/github"
2625
import { getSyncRepoName } from "@/lib/sync/repo-utils"
2726
import { filterSyncData, mergeSyncData } from "@/config/sync-exclusions"
2827
import { confirm } from "@tauri-apps/plugin-dialog"
2928

29+
// ============ 通用辅助函数 ============
30+
function encodePath(path: string, filename?: string): string {
31+
const fullPath = filename ? `${path}/${filename}` : path
32+
return fullPath.replace(/\s/g, '_').split('/').map(segment => encodeURIComponent(segment)).join('/')
33+
}
34+
35+
async function requestGitHub(method: string, url: string, body?: object) {
36+
const store = await Store.load('store.json')
37+
const accessToken = await store.get<string>('accessToken')
38+
39+
const headers = new Headers()
40+
headers.append('Authorization', `Bearer ${accessToken}`)
41+
headers.append('Accept', 'application/vnd.github+json')
42+
headers.append('X-GitHub-Api-Version', '2022-11-28')
43+
headers.append('Content-Type', 'application/json')
44+
45+
const response = await fetch(url, { method, headers, body: body ? JSON.stringify(body) : undefined })
46+
47+
if (response.status >= 200 && response.status < 300) {
48+
return method === 'GET' ? await response.json() : await response.json()
49+
}
50+
if (method === 'GET') return null
51+
52+
const errorData = await response.json()
53+
throw { status: response.status, message: errorData.message || 'Request failed' }
54+
}
55+
56+
async function requestGitee(method: string, url: string, body?: object) {
57+
const store = await Store.load('store.json')
58+
const accessToken = await store.get<string>('accessToken')
59+
60+
const headers = new Headers()
61+
headers.append('Content-Type', 'application/json')
62+
63+
const response = await fetch(url, { method, headers, body: body ? JSON.stringify(body) : undefined })
64+
65+
if (response.status >= 200 && response.status < 300) {
66+
return method === 'GET' ? await response.json() : await response.json()
67+
}
68+
if (method === 'GET') return null
69+
70+
const errorData = await response.json()
71+
throw { status: response.status, message: errorData.message || 'Request failed' }
72+
}
73+
74+
async function requestGitLab(method: string, url: string, body?: object) {
75+
const store = await Store.load('store.json')
76+
const accessToken = await store.get<string>('accessToken')
77+
78+
const headers = new Headers()
79+
headers.append('PRIVATE-TOKEN', accessToken as string)
80+
headers.append('Content-Type', 'application/json')
81+
82+
const response = await fetch(url, { method, headers, body: body ? JSON.stringify(body) : undefined })
83+
84+
if (response.status >= 200 && response.status < 300) {
85+
return method === 'GET' ? await response.json() : await response.json()
86+
}
87+
if (method === 'GET') return null
88+
89+
const errorData = await response.json()
90+
throw { status: response.status, message: errorData.message || 'Request failed' }
91+
}
92+
93+
async function requestGitea(method: string, url: string, body?: object) {
94+
const store = await Store.load('store.json')
95+
const accessToken = await store.get<string>('accessToken')
96+
97+
const headers = new Headers()
98+
headers.append('Authorization', `token ${accessToken}`)
99+
headers.append('Content-Type', 'application/json')
100+
101+
const response = await fetch(url, { method, headers, body: body ? JSON.stringify(body) : undefined })
102+
103+
if (response.status >= 200 && response.status < 300) {
104+
return method === 'GET' ? await response.json() : await response.json()
105+
}
106+
if (method === 'GET') return null
107+
108+
const errorData = await response.json()
109+
throw { status: response.status, message: errorData.message || 'Request failed' }
110+
}
111+
112+
// ============ GitHub 上传/下载函数 ============
113+
async function githubUpload({ file, path, filename, sha, repo, accessToken, githubUsername }: {
114+
file: string, path: string, filename: string, sha?: string, repo: string, accessToken: string, githubUsername: string
115+
}) {
116+
const url = `https://api.github.com/repos/${githubUsername}/${repo}/contents/${encodePath(path, filename)}`
117+
return requestGitHub('PUT', url, { message: `Upload ${filename}`, content: file, sha })
118+
}
119+
120+
async function githubGetFile({ path, repo, accessToken, githubUsername }: {
121+
path: string, repo: string, accessToken: string, githubUsername: string
122+
}) {
123+
const url = `https://api.github.com/repos/${githubUsername}/${repo}/contents/${encodePath(path)}`
124+
return requestGitHub('GET', url)
125+
}
126+
127+
// ============ Gitee 上传/下载函数 ============
128+
async function giteeUpload({ file, path, filename, sha, repo, accessToken, giteeUsername }: {
129+
file: string, path: string, filename: string, sha?: string, repo: string, accessToken: string, giteeUsername: string
130+
}) {
131+
const url = `https://gitee.com/api/v5/repos/${giteeUsername}/${repo}/contents/${encodePath(path, filename)}`
132+
return requestGitee(sha ? 'PUT' : 'POST', url, { access_token: accessToken, content: file, message: `Upload ${filename}`, branch: 'master', sha })
133+
}
134+
135+
async function giteeGetFile({ path, repo, accessToken, giteeUsername }: {
136+
path: string, repo: string, accessToken: string, giteeUsername: string
137+
}) {
138+
const url = `https://gitee.com/api/v5/repos/${giteeUsername}/${repo}/contents/${encodePath(path)}?access_token=${accessToken}`
139+
return requestGitee('GET', url)
140+
}
141+
142+
// ============ GitLab 上传/下载函数 ============
143+
async function gitlabUpload({ file, path, filename, sha: _sha, accessToken, projectId }: {
144+
file: string, path: string, filename: string, sha?: string, accessToken: string, projectId: string
145+
}) {
146+
const url = `https://gitlab.com/api/v4/projects/${projectId}/repository/files/${encodePath(path, filename)}`
147+
return requestGitLab('PUT', url, { branch: 'main', content: file, commit_message: `Upload ${filename}`, encoding: 'base64' })
148+
}
149+
150+
async function gitlabGetFile({ path, accessToken, projectId }: {
151+
path: string, accessToken: string, projectId: string
152+
}) {
153+
const url = `https://gitlab.com/api/v4/projects/${projectId}/repository/files/${encodePath(path)}?ref=main`
154+
return requestGitLab('GET', url)
155+
}
156+
157+
// ============ Gitea 上传/下载函数 ============
158+
async function giteaUpload({ file, path, filename, sha, repo, accessToken, giteaUsername }: {
159+
file: string, path: string, filename: string, sha?: string, repo: string, accessToken: string, giteaUsername: string
160+
}) {
161+
const url = `https://gitea.com/api/v1/repos/${giteaUsername}/${repo}/contents/${encodePath(path, filename)}`
162+
return requestGitea('PUT', url, { content: file, message: `Upload ${filename}`, branch: 'main', sha })
163+
}
164+
165+
async function giteaGetFile({ path, repo, accessToken, giteaUsername }: {
166+
path: string, repo: string, accessToken: string, giteaUsername: string
167+
}) {
168+
const url = `https://gitea.com/api/v1/repos/${giteaUsername}/${repo}/contents/${encodePath(path)}?ref=main`
169+
return requestGitea('GET', url)
170+
}
171+
30172
export function SyncToggle() {
31173
const t = useTranslations()
32174
const username = useUsername()
@@ -54,7 +196,6 @@ export function SyncToggle() {
54196
// 上传数据(tags, marks, chats)
55197
const tagRes = await uploadTags()
56198
const markRes = await uploadMarks()
57-
const chatRes = await uploadChats()
58199

59200
// 上传配置
60201
const path = '.settings'
@@ -71,64 +212,72 @@ export function SyncToggle() {
71212
const filteredContent = JSON.stringify(syncableSettings, null, 2)
72213
const file = new TextEncoder().encode(filteredContent)
73214

74-
const primaryBackupMethod = await store.get('primaryBackupMethod')
75-
let files: any;
215+
const primaryBackupMethod = await store.get<string>('primaryBackupMethod')
216+
const accessToken = await store.get<string>('accessToken')
217+
const githubUsername = await store.get<string>('githubUsername')
218+
const giteeUsername = await store.get<string>('giteeUsername')
219+
const gitlabProjectId = await store.get<string>(`gitlab_${await getSyncRepoName('gitlab')}_project_id`)
220+
const giteaUsername = await store.get<string>('giteaUsername')
76221
let settingsRes;
77-
222+
78223
switch (primaryBackupMethod) {
79-
case 'github':
224+
case 'github': {
80225
const githubRepo = await getSyncRepoName('github')
81-
files = await githubGetFiles({ path: `${path}/${filename}`, repo: githubRepo })
82-
settingsRes = await uploadGithubFile({
226+
const existingFile = await githubGetFile({ path: `${path}/${filename}`, repo: githubRepo, accessToken: accessToken!, githubUsername: githubUsername! })
227+
settingsRes = await githubUpload({
83228
file: uint8ArrayToBase64(file),
84-
repo: githubRepo,
85229
path,
86230
filename,
87-
sha: files?.sha,
231+
sha: existingFile?.sha,
232+
repo: githubRepo,
233+
accessToken: accessToken!,
234+
githubUsername: githubUsername!,
88235
})
89236
break;
90-
case 'gitee':
237+
}
238+
case 'gitee': {
91239
const giteeRepo = await getSyncRepoName('gitee')
92-
files = await giteeGetFiles({ path: `${path}/${filename}`, repo: giteeRepo })
93-
settingsRes = await uploadGiteeFile({
240+
const existingFile = await giteeGetFile({ path: `${path}/${filename}`, repo: giteeRepo, accessToken: accessToken!, giteeUsername: giteeUsername! })
241+
settingsRes = await giteeUpload({
94242
file: uint8ArrayToBase64(file),
95-
repo: giteeRepo,
96243
path,
97244
filename,
98-
sha: files?.sha,
245+
sha: existingFile?.sha,
246+
repo: giteeRepo,
247+
accessToken: accessToken!,
248+
giteeUsername: giteeUsername!,
99249
})
100250
break;
101-
case 'gitlab':
102-
const gitlabRepo = await getSyncRepoName('gitlab')
103-
files = await gitlabGetFiles({ path, repo: gitlabRepo })
104-
const storeFile = Array.isArray(files)
105-
? files.find(file => file.name === filename)
106-
: (files?.name === filename ? files : undefined)
107-
settingsRes = await uploadGitlabFile({
251+
}
252+
case 'gitlab': {
253+
const existingFile = await gitlabGetFile({ path: `${path}/${filename}`, accessToken: accessToken!, projectId: gitlabProjectId! })
254+
settingsRes = await gitlabUpload({
108255
file: uint8ArrayToBase64(file),
109-
repo: gitlabRepo,
110256
path,
111257
filename,
112-
sha: storeFile?.sha || '',
258+
sha: existingFile?.sha,
259+
accessToken: accessToken!,
260+
projectId: gitlabProjectId!,
113261
})
114262
break;
115-
case 'gitea':
263+
}
264+
case 'gitea': {
116265
const giteaRepo = await getSyncRepoName('gitea')
117-
files = await giteaGetFiles({ path, repo: giteaRepo })
118-
const giteaStoreFile = Array.isArray(files)
119-
? files.find(file => file.name === filename)
120-
: (files?.name === filename ? files : undefined)
121-
settingsRes = await uploadGiteaFile({
266+
const existingFile = await giteaGetFile({ path: `${path}/${filename}`, repo: giteaRepo, accessToken: accessToken!, giteaUsername: giteaUsername! })
267+
settingsRes = await giteaUpload({
122268
file: uint8ArrayToBase64(file),
123-
repo: giteaRepo,
124269
path,
125270
filename,
126-
sha: giteaStoreFile?.sha || '',
271+
sha: existingFile?.sha,
272+
repo: giteaRepo,
273+
accessToken: accessToken!,
274+
giteaUsername: giteaUsername!,
127275
})
128276
break;
277+
}
129278
}
130279

131-
if (tagRes && markRes && chatRes && settingsRes) {
280+
if (tagRes && markRes && settingsRes) {
132281
toast({
133282
description: t('record.mark.uploadSuccess'),
134283
})
@@ -153,9 +302,8 @@ export function SyncToggle() {
153302
// 下载数据(tags, marks, chats)
154303
const tagRes = await downloadTags()
155304
const markRes = await downloadMarks()
156-
const chatRes = await downloadChats()
157305

158-
if (tagRes && markRes && chatRes) {
306+
if (tagRes && markRes) {
159307
await fetchTags()
160308
await fetchMarks()
161309
init(currentTagId)
@@ -172,30 +320,38 @@ export function SyncToggle() {
172320
localSettings[key] = value
173321
}
174322

175-
const primaryBackupMethod = await store.get('primaryBackupMethod')
176-
let file;
177-
323+
const primaryBackupMethod = await store.get<string>('primaryBackupMethod')
324+
const accessToken = await store.get<string>('accessToken')
325+
const githubUsername = await store.get<string>('githubUsername')
326+
const giteeUsername = await store.get<string>('giteeUsername')
327+
const gitlabProjectId = await store.get<string>(`gitlab_${await getSyncRepoName('gitlab')}_project_id`)
328+
const giteaUsername = await store.get<string>('giteaUsername')
329+
let remoteFile;
330+
178331
switch (primaryBackupMethod) {
179-
case 'github':
180-
const githubRepo2 = await getSyncRepoName('github')
181-
file = await githubGetFiles({ path: `${path}/${filename}`, repo: githubRepo2 })
332+
case 'github': {
333+
const githubRepo = await getSyncRepoName('github')
334+
remoteFile = await githubGetFile({ path: `${path}/${filename}`, repo: githubRepo, accessToken: accessToken!, githubUsername: githubUsername! })
182335
break;
183-
case 'gitee':
184-
const giteeRepo2 = await getSyncRepoName('gitee')
185-
file = await giteeGetFiles({ path: `${path}/${filename}`, repo: giteeRepo2 })
336+
}
337+
case 'gitee': {
338+
const giteeRepo = await getSyncRepoName('gitee')
339+
remoteFile = await giteeGetFile({ path: `${path}/${filename}`, repo: giteeRepo, accessToken: accessToken!, giteeUsername: giteeUsername! })
186340
break;
187-
case 'gitlab':
188-
const gitlabRepo2 = await getSyncRepoName('gitlab')
189-
file = await gitlabGetFileContent({ path: `${path}/${filename}`, ref: 'main', repo: gitlabRepo2 })
341+
}
342+
case 'gitlab': {
343+
remoteFile = await gitlabGetFile({ path: `${path}/${filename}`, accessToken: accessToken!, projectId: gitlabProjectId! })
190344
break;
191-
case 'gitea':
192-
const giteaRepo2 = await getSyncRepoName('gitea')
193-
file = await giteaGetFileContent({ path: `${path}/${filename}`, ref: 'main', repo: giteaRepo2 })
345+
}
346+
case 'gitea': {
347+
const giteaRepo = await getSyncRepoName('gitea')
348+
remoteFile = await giteaGetFile({ path: `${path}/${filename}`, repo: giteaRepo, accessToken: accessToken!, giteaUsername: giteaUsername! })
194349
break;
350+
}
195351
}
196-
197-
if (file) {
198-
const configJson = decodeBase64ToString(file.content)
352+
353+
if (remoteFile) {
354+
const configJson = decodeBase64ToString(remoteFile.content)
199355
const remoteSettings = JSON.parse(configJson)
200356

201357
const mergedSettings = mergeSyncData(localSettings, remoteSettings)

src/stores/chat.ts

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -460,26 +460,25 @@ const useChatStore = create<ChatState>((set, get) => ({
460460
let result = false
461461
let files: any;
462462
let res;
463+
const fullPath = `${path}/${filename}`;
463464
switch (primaryBackupMethod) {
464465
case 'github':
465466
const githubRepo = await getSyncRepoName('github')
466-
files = await githubGetFiles({ path: `${path}/${filename}`, repo: githubRepo })
467+
files = await githubGetFiles({ path: fullPath, repo: githubRepo })
467468
res = await uploadGithubFile({
468469
file: jsonToBase64(chats),
469470
repo: githubRepo,
470-
path,
471-
filename,
471+
path: fullPath,
472472
sha: files?.sha,
473473
})
474474
break;
475475
case 'gitee':
476476
const giteeRepo = await getSyncRepoName('gitee')
477-
files = await giteeGetFiles({ path: `${path}/${filename}`, repo: giteeRepo })
477+
files = await giteeGetFiles({ path: fullPath, repo: giteeRepo })
478478
res = await uploadGiteeFile({
479479
file: jsonToBase64(chats),
480480
repo: giteeRepo,
481-
path,
482-
filename,
481+
path: fullPath,
483482
sha: files?.sha,
484483
})
485484
break;

0 commit comments

Comments
 (0)