2023-05-01 13:20:25 -04:00
|
|
|
import { app } from 'electron'
|
|
|
|
import fs from 'fs-extra'
|
|
|
|
import path from 'path'
|
|
|
|
import axios from 'axios'
|
|
|
|
import db from '~/main/apis/core/datastore'
|
|
|
|
import { HttpsProxyAgent } from 'hpagent'
|
|
|
|
import { Octokit } from '@octokit/rest'
|
|
|
|
import logger from 'apis/core/picgo/logger'
|
|
|
|
|
2023-05-02 00:03:21 -04:00
|
|
|
interface SyncConfig {
|
|
|
|
type: string
|
|
|
|
file: string
|
|
|
|
username: string
|
|
|
|
repo: string
|
|
|
|
branch: string
|
|
|
|
token: string
|
|
|
|
proxy?: string,
|
|
|
|
interval?: number
|
|
|
|
}
|
|
|
|
|
2023-05-01 13:20:25 -04:00
|
|
|
const STORE_PATH = app.getPath('userData')
|
|
|
|
|
|
|
|
const configFileNames = [
|
|
|
|
'data.json',
|
|
|
|
'data.bak.json',
|
|
|
|
'manage.json',
|
|
|
|
'manage.bak.json',
|
|
|
|
'piclist-remote-notice.json',
|
|
|
|
'UpDownTaskQueue.json'
|
|
|
|
]
|
|
|
|
|
2023-05-02 00:03:21 -04:00
|
|
|
function getOctokit (syncConfig: SyncConfig) {
|
2023-05-01 13:20:25 -04:00
|
|
|
const { token, proxy } = syncConfig
|
|
|
|
return new Octokit({
|
|
|
|
auth: token,
|
|
|
|
request: {
|
|
|
|
agent: proxy
|
|
|
|
? new HttpsProxyAgent({
|
|
|
|
keepAlive: true,
|
|
|
|
keepAliveMsecs: 1000,
|
|
|
|
rejectUnauthorized: false,
|
|
|
|
proxy: proxy.replace('127.0.0.1', 'localhost'),
|
|
|
|
scheduling: 'lifo'
|
|
|
|
})
|
|
|
|
: undefined
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
function getSyncConfig () {
|
|
|
|
const syncConfig = db.get('settings.sync') || {
|
|
|
|
type: 'github',
|
|
|
|
username: '',
|
|
|
|
repo: '',
|
|
|
|
branch: '',
|
|
|
|
token: '',
|
|
|
|
proxy: ''
|
|
|
|
}
|
|
|
|
return syncConfig
|
|
|
|
}
|
|
|
|
|
2023-05-02 00:03:21 -04:00
|
|
|
function syncConfigValidator (syncConfig: SyncConfig) {
|
2023-05-01 13:20:25 -04:00
|
|
|
const { type, file, username, repo, branch, token } = syncConfig
|
|
|
|
return type && file && username && repo && branch && token
|
|
|
|
}
|
|
|
|
|
2023-05-02 00:03:21 -04:00
|
|
|
async function getModifiedTime (syncConfig: SyncConfig, filePath: string) {
|
2023-05-01 13:20:25 -04:00
|
|
|
const { username, repo, branch, token, type } = syncConfig
|
|
|
|
if (type === 'gitee') {
|
|
|
|
const url = `https://gitee.com/api/v5/repos/${username}/${repo}/commits`
|
|
|
|
const res = await axios.get(url, {
|
|
|
|
params: {
|
|
|
|
access_token: token,
|
|
|
|
ref: branch,
|
|
|
|
path: filePath
|
|
|
|
}
|
|
|
|
})
|
|
|
|
const data = res.data
|
|
|
|
if (data.length > 0) {
|
|
|
|
return data[0].commit.committer.date
|
|
|
|
} else {
|
|
|
|
return null
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
const octokit = getOctokit(syncConfig)
|
|
|
|
try {
|
|
|
|
const res = await octokit.rest.repos.listCommits({
|
|
|
|
owner: username,
|
|
|
|
repo,
|
|
|
|
ref: branch,
|
|
|
|
path: filePath,
|
|
|
|
per_page: 1
|
|
|
|
})
|
|
|
|
if (res.status === 200) {
|
|
|
|
return res.data.length > 0 ? res.data[0].commit.committer?.date : null
|
|
|
|
} else {
|
|
|
|
return null
|
|
|
|
}
|
|
|
|
} catch (error: any) {
|
|
|
|
logger.error(error)
|
|
|
|
return null
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
async function getModifiedTimeOfLocal (filePath: string) {
|
|
|
|
if (!fs.existsSync(filePath)) {
|
|
|
|
return new Date(0)
|
|
|
|
}
|
|
|
|
const stat = await fs.stat(filePath)
|
|
|
|
return stat.mtime
|
|
|
|
}
|
|
|
|
|
2023-05-02 00:03:21 -04:00
|
|
|
async function compareNewerFile (syncConfig: SyncConfig, fileName: string): Promise<'upload' | 'download' | 'update' | undefined> {
|
2023-05-01 13:20:25 -04:00
|
|
|
const localFilePath = path.join(STORE_PATH, fileName)
|
|
|
|
const remoteModifiedTime = await getModifiedTime(syncConfig, fileName)
|
|
|
|
if (remoteModifiedTime === null) {
|
|
|
|
return 'upload'
|
|
|
|
}
|
|
|
|
const localModifiedTime = await getModifiedTimeOfLocal(localFilePath)
|
|
|
|
if (remoteModifiedTime && localModifiedTime) {
|
|
|
|
return Date.parse(remoteModifiedTime) > localModifiedTime.getTime() ? 'download' : 'update'
|
|
|
|
} else {
|
|
|
|
throw new Error('get modified time failed')
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-05-02 00:03:21 -04:00
|
|
|
async function uploadLocalToRemote (syncConfig: SyncConfig, fileName: string) {
|
2023-05-01 13:20:25 -04:00
|
|
|
const localFilePath = path.join(STORE_PATH, fileName)
|
|
|
|
const { username, repo, branch, token, type } = syncConfig
|
|
|
|
if (type === 'gitee') {
|
|
|
|
const url = `https://gitee.com/api/v5/repos/${username}/${repo}/contents/${fileName}`
|
|
|
|
const res = await axios.post(url, {
|
|
|
|
access_token: token,
|
|
|
|
branch,
|
|
|
|
content: fs.readFileSync(localFilePath, { encoding: 'base64' }),
|
|
|
|
message: `upload ${fileName} from PicList`
|
|
|
|
})
|
|
|
|
return res.status >= 200 && res.status < 300
|
|
|
|
} else {
|
|
|
|
const octokit = getOctokit(syncConfig)
|
|
|
|
try {
|
|
|
|
const res = await octokit.rest.repos.createOrUpdateFileContents({
|
|
|
|
owner: username,
|
|
|
|
repo,
|
|
|
|
path: fileName,
|
|
|
|
message: `upload ${fileName} from PicList`,
|
|
|
|
content: fs.readFileSync(localFilePath, { encoding: 'base64' }),
|
|
|
|
branch
|
|
|
|
})
|
|
|
|
return res.status === 200
|
|
|
|
} catch (error: any) {
|
|
|
|
logger.error(error)
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-05-02 00:03:21 -04:00
|
|
|
async function updateLocalToRemote (syncConfig: SyncConfig, fileName: string) {
|
2023-05-01 13:20:25 -04:00
|
|
|
const localFilePath = path.join(STORE_PATH, fileName)
|
|
|
|
const { username, repo, branch, token, type } = syncConfig
|
|
|
|
if (type === 'gitee') {
|
|
|
|
const url = `https://gitee.com/api/v5/repos/${username}/${repo}/contents/${fileName}`
|
|
|
|
const shaRes = await axios.get(url, {
|
|
|
|
params: {
|
|
|
|
access_token: token,
|
|
|
|
ref: branch
|
|
|
|
}
|
|
|
|
})
|
|
|
|
if (shaRes.status < 200 || shaRes.status > 300) {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
const sha = shaRes.data.sha
|
|
|
|
const res = await axios.put(url, {
|
|
|
|
owner: username,
|
|
|
|
repo,
|
|
|
|
path: fileName,
|
|
|
|
message: `update ${fileName} from PicList`,
|
|
|
|
content: fs.readFileSync(localFilePath, { encoding: 'base64' }),
|
|
|
|
branch,
|
|
|
|
sha,
|
|
|
|
access_token: token
|
|
|
|
})
|
|
|
|
if (res.status >= 200 && res.status < 300) {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
} else {
|
|
|
|
const octokit = getOctokit(syncConfig)
|
|
|
|
try {
|
|
|
|
const shaRes = await octokit.rest.repos.getContent({
|
|
|
|
owner: username,
|
|
|
|
repo,
|
|
|
|
path: fileName,
|
|
|
|
ref: branch
|
|
|
|
})
|
|
|
|
if (shaRes.status !== 200) {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
const data = shaRes.data as any
|
|
|
|
const sha = data.sha
|
|
|
|
const res = await octokit.rest.repos.createOrUpdateFileContents({
|
|
|
|
owner: username,
|
|
|
|
repo,
|
|
|
|
path: fileName,
|
|
|
|
message: `update ${fileName} from PicList`,
|
|
|
|
content: fs.readFileSync(localFilePath, { encoding: 'base64' }),
|
|
|
|
branch,
|
|
|
|
sha
|
|
|
|
})
|
|
|
|
return res.status === 200
|
|
|
|
} catch (error: any) {
|
|
|
|
logger.error(error)
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-05-02 00:03:21 -04:00
|
|
|
async function downloadRemoteToLocal (syncConfig: SyncConfig, fileName: string) {
|
2023-05-01 13:20:25 -04:00
|
|
|
const localFilePath = path.join(STORE_PATH, fileName)
|
|
|
|
const { username, repo, branch, token, proxy, type } = syncConfig
|
|
|
|
if (type === 'gitee') {
|
|
|
|
const url = `https://gitee.com/api/v5/repos/${username}/${repo}/contents/${fileName}`
|
|
|
|
const res = await axios.get(url, {
|
|
|
|
params: {
|
|
|
|
access_token: token,
|
|
|
|
ref: branch
|
|
|
|
}
|
|
|
|
})
|
|
|
|
if (res.status >= 200 && res.status < 300) {
|
|
|
|
const content = res.data.content
|
|
|
|
await fs.writeFile(localFilePath, Buffer.from(content, 'base64'))
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
} else {
|
|
|
|
const octokit = getOctokit(syncConfig)
|
|
|
|
try {
|
|
|
|
const res = await octokit.rest.repos.getContent({
|
|
|
|
owner: username,
|
|
|
|
repo,
|
|
|
|
path: fileName,
|
|
|
|
ref: branch
|
|
|
|
})
|
|
|
|
if (res.status === 200) {
|
|
|
|
const data = res.data as any
|
|
|
|
const downloadUrl = data.download_url
|
|
|
|
const downloadRes = await axios.get(downloadUrl, {
|
|
|
|
httpsAgent: proxy
|
|
|
|
? new HttpsProxyAgent({
|
|
|
|
keepAlive: true,
|
|
|
|
keepAliveMsecs: 1000,
|
|
|
|
rejectUnauthorized: false,
|
|
|
|
proxy: proxy.replace('127.0.0.1', 'localhost'),
|
|
|
|
scheduling: 'lifo'
|
|
|
|
})
|
|
|
|
: undefined
|
|
|
|
})
|
|
|
|
if (downloadRes.status >= 200 && downloadRes.status < 300) {
|
|
|
|
await fs.writeFile(localFilePath, JSON.stringify(downloadRes.data, null, 2))
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
} catch (error: any) {
|
|
|
|
logger.error(error)
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-05-02 00:03:21 -04:00
|
|
|
async function syncFile (syncConfig: SyncConfig, fileName: string) {
|
2023-05-01 13:20:25 -04:00
|
|
|
const compareResult = await compareNewerFile(syncConfig, fileName)
|
|
|
|
let result = false
|
|
|
|
if (compareResult === 'upload') {
|
|
|
|
result = await uploadLocalToRemote(syncConfig, fileName)
|
|
|
|
} else if (compareResult === 'update') {
|
|
|
|
result = await updateLocalToRemote(syncConfig, fileName)
|
|
|
|
} else if (compareResult === 'download') {
|
|
|
|
result = await downloadRemoteToLocal(syncConfig, fileName)
|
|
|
|
}
|
|
|
|
return result
|
|
|
|
}
|
|
|
|
|
2023-05-02 00:03:21 -04:00
|
|
|
async function syncAllFiles (syncConfig: SyncConfig) {
|
2023-05-01 13:20:25 -04:00
|
|
|
for (const file of configFileNames) {
|
|
|
|
try {
|
|
|
|
const result = await syncFile(syncConfig, file)
|
|
|
|
if (result) {
|
|
|
|
logger.info(`sync file ${file} success`)
|
|
|
|
}
|
|
|
|
} catch (error: any) {
|
|
|
|
logger.error(`sync file ${file} failed`)
|
|
|
|
logger.error(error)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-05-02 00:03:21 -04:00
|
|
|
async function syncFunc () {
|
|
|
|
const syncConfig = await getSyncConfig()
|
|
|
|
if (!syncConfigValidator(syncConfig)) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
await syncAllFiles(syncConfig)
|
|
|
|
logger.info(`sync all files at ${new Date().toLocaleString()}`)
|
|
|
|
}
|
|
|
|
|
2023-05-01 13:20:25 -04:00
|
|
|
async function syncInterval () {
|
|
|
|
const syncConfig = await getSyncConfig()
|
|
|
|
if (!syncConfigValidator(syncConfig)) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
const syncFunc = async () => {
|
|
|
|
await syncAllFiles(syncConfig)
|
|
|
|
logger.info(`sync all files at ${new Date().toLocaleString()}`)
|
|
|
|
}
|
|
|
|
await syncFunc()
|
2023-05-02 00:03:21 -04:00
|
|
|
const interval = Number(syncConfig.interval) || 60
|
2023-05-01 13:20:25 -04:00
|
|
|
setInterval(async () => {
|
|
|
|
syncFunc()
|
2023-05-02 00:03:21 -04:00
|
|
|
}, 1000 * 60 * interval)
|
2023-05-01 13:20:25 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
export {
|
2023-05-02 00:03:21 -04:00
|
|
|
syncFunc,
|
2023-05-01 13:20:25 -04:00
|
|
|
syncInterval
|
|
|
|
}
|