11"use client"
22
3+ /* eslint-disable @typescript-eslint/no-explicit-any, @typescript-eslint/no-unused-vars */
4+
35import * as React from "react"
46import { DownloadCloud , Loader2 , UploadCloud , CloudSync } from "lucide-react"
57import { useTranslations } from 'next-intl'
@@ -19,14 +21,154 @@ import useTagStore from "@/stores/tag"
1921import useChatStore from "@/stores/chat"
2022import useSettingStore from "@/stores/setting"
2123import { 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"
2625import { getSyncRepoName } from "@/lib/sync/repo-utils"
2726import { filterSyncData , mergeSyncData } from "@/config/sync-exclusions"
2827import { 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+
30172export 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 )
0 commit comments