Skip to content

Commit 1c9c25a

Browse files
committed
feat: implement auto updater
1 parent 979e588 commit 1c9c25a

File tree

13 files changed

+1293
-2
lines changed

13 files changed

+1293
-2
lines changed

app.go

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import (
1919
"github.com/lich0821/ccNexus/internal/proxy"
2020
"github.com/lich0821/ccNexus/internal/storage"
2121
"github.com/lich0821/ccNexus/internal/tray"
22+
"github.com/lich0821/ccNexus/internal/updater"
2223
"github.com/lich0821/ccNexus/internal/webdav"
2324
"github.com/wailsapp/wails/v2/pkg/runtime"
2425
)
@@ -62,6 +63,7 @@ type App struct {
6263
configPath string
6364
ctxMutex sync.RWMutex
6465
trayIcon []byte
66+
updater *updater.Updater
6567
}
6668

6769
// NewApp creates a new App application struct
@@ -2239,3 +2241,130 @@ func (a *App) GenerateMockArchives(monthsCount int) string {
22392241
data, _ := json.Marshal(result)
22402242
return string(data)
22412243
}
2244+
2245+
// CheckForUpdates checks if a new version is available
2246+
func (a *App) CheckForUpdates() string {
2247+
logger.Info("CheckForUpdates called, current version: %s", a.GetVersion())
2248+
if a.updater == nil {
2249+
a.updater = updater.New(a.GetVersion())
2250+
}
2251+
info, err := a.updater.CheckForUpdates()
2252+
if err != nil {
2253+
logger.Error("CheckForUpdates failed: %v", err)
2254+
result := map[string]interface{}{
2255+
"success": false,
2256+
"error": err.Error(),
2257+
}
2258+
data, _ := json.Marshal(result)
2259+
return string(data)
2260+
}
2261+
2262+
logger.Info("Update check result: hasUpdate=%v, latest=%s", info.HasUpdate, info.LatestVersion)
2263+
2264+
// Update last check time
2265+
updateCfg := a.config.GetUpdate()
2266+
updateCfg.LastCheckTime = time.Now().Format(time.RFC3339)
2267+
a.config.UpdateUpdate(updateCfg)
2268+
2269+
if a.storage != nil {
2270+
configAdapter := storage.NewConfigStorageAdapter(a.storage)
2271+
a.config.SaveToStorage(configAdapter)
2272+
}
2273+
2274+
result := map[string]interface{}{
2275+
"success": true,
2276+
"info": info,
2277+
}
2278+
data, _ := json.Marshal(result)
2279+
logger.Debug("CheckForUpdates response: %s", string(data))
2280+
return string(data)
2281+
}
2282+
2283+
// GetUpdateSettings returns update settings
2284+
func (a *App) GetUpdateSettings() string {
2285+
updateCfg := a.config.GetUpdate()
2286+
data, _ := json.Marshal(updateCfg)
2287+
return string(data)
2288+
}
2289+
2290+
// SetUpdateSettings updates update settings
2291+
func (a *App) SetUpdateSettings(autoCheck bool, checkInterval int) error {
2292+
updateCfg := a.config.GetUpdate()
2293+
updateCfg.AutoCheck = autoCheck
2294+
updateCfg.CheckInterval = checkInterval
2295+
2296+
a.config.UpdateUpdate(updateCfg)
2297+
2298+
if a.storage != nil {
2299+
configAdapter := storage.NewConfigStorageAdapter(a.storage)
2300+
if err := a.config.SaveToStorage(configAdapter); err != nil {
2301+
return fmt.Errorf("failed to save update settings: %w", err)
2302+
}
2303+
}
2304+
2305+
logger.Info("Update settings changed: autoCheck=%v, interval=%d hours", autoCheck, checkInterval)
2306+
return nil
2307+
}
2308+
2309+
// SkipVersion skips a specific version
2310+
func (a *App) SkipVersion(version string) error {
2311+
updateCfg := a.config.GetUpdate()
2312+
updateCfg.SkippedVersion = version
2313+
2314+
a.config.UpdateUpdate(updateCfg)
2315+
2316+
if a.storage != nil {
2317+
configAdapter := storage.NewConfigStorageAdapter(a.storage)
2318+
if err := a.config.SaveToStorage(configAdapter); err != nil {
2319+
return fmt.Errorf("failed to save skipped version: %w", err)
2320+
}
2321+
}
2322+
2323+
logger.Info("Version skipped: %s", version)
2324+
return nil
2325+
}
2326+
2327+
// DownloadUpdate downloads the update file
2328+
func (a *App) DownloadUpdate(url, filename string) error {
2329+
if a.updater == nil {
2330+
a.updater = updater.New(a.GetVersion())
2331+
}
2332+
return a.updater.DownloadUpdate(url, filename)
2333+
}
2334+
2335+
// GetDownloadProgress returns download progress
2336+
func (a *App) GetDownloadProgress() string {
2337+
if a.updater == nil {
2338+
a.updater = updater.New(a.GetVersion())
2339+
}
2340+
progress := a.updater.GetDownloadProgress()
2341+
data, _ := json.Marshal(progress)
2342+
return string(data)
2343+
}
2344+
2345+
// InstallUpdate installs the downloaded update
2346+
func (a *App) InstallUpdate(filePath string) string {
2347+
if a.updater == nil {
2348+
a.updater = updater.New(a.GetVersion())
2349+
}
2350+
result, err := a.updater.InstallUpdate(filePath)
2351+
if err != nil {
2352+
errorResult := map[string]interface{}{
2353+
"success": false,
2354+
"error": err.Error(),
2355+
}
2356+
data, _ := json.Marshal(errorResult)
2357+
return string(data)
2358+
}
2359+
data, _ := json.Marshal(result)
2360+
return string(data)
2361+
}
2362+
2363+
// SendUpdateNotification sends a system notification for updates
2364+
func (a *App) SendUpdateNotification(title, message string) error {
2365+
err := updater.SendNotification(title, message)
2366+
if err != nil {
2367+
logger.Error("Failed to send notification: %v", err)
2368+
}
2369+
return err
2370+
}

frontend/src/i18n/en.js

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -268,14 +268,45 @@ export default {
268268
useRemoteDesc: 'Remote configuration will overwrite local conflicting endpoints',
269269
keepLocalDesc: 'Keep local configuration, only add new endpoints from remote'
270270
},
271+
update: {
272+
checkForUpdates: 'Check for Updates',
273+
newVersionAvailable: 'New Version Available',
274+
currentVersion: 'Current Version',
275+
latestVersion: 'Latest Version',
276+
releaseDate: 'Release Date',
277+
changelog: 'Changelog',
278+
downloadUpdate: 'Download Update',
279+
installUpdate: 'Install Update',
280+
skipVersion: 'Skip This Version',
281+
remindLater: 'Remind Me Later',
282+
downloading: 'Downloading',
283+
downloadComplete: 'Download Complete',
284+
downloadFailed: 'Download Failed',
285+
installFailed: 'Installation Failed',
286+
autoCheck: 'Auto Check for Updates',
287+
checkInterval: 'Check Interval',
288+
upToDate: 'You are up to date',
289+
checkFailed: 'Failed to check for updates',
290+
noAutoCheck: "No Auto Check",
291+
everyHour: 'Every Hour',
292+
every6Hours: 'Every 6 Hours',
293+
everyDay: 'Every Day',
294+
everyWeek: 'Every Week',
295+
extractComplete: 'Extraction Complete',
296+
extractPath: 'File Location',
297+
install_instructions_windows: '1. Close current ccNexus application<br>2. Run the new ccNexus.exe',
298+
install_instructions_macos: '1. Close current ccNexus application<br>2. Drag the new ccNexus.app to Applications folder',
299+
install_instructions_linux: '1. Close current ccNexus application<br>2. Run the new ccNexus executable'
300+
},
271301
common: {
272302
ok: 'OK',
273303
cancel: 'Cancel',
274304
yes: 'Yes',
275305
no: 'No',
276306
confirm: 'Confirm',
277307
delete: 'Delete',
278-
confirmDeleteTitle: 'Confirm Deletion'
308+
confirmDeleteTitle: 'Confirm Deletion',
309+
close: 'Close'
279310
},
280311
tips: [
281312
'Tip: You can add multiple API endpoints for automatic failover',

frontend/src/i18n/zh-CN.js

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -268,14 +268,45 @@ export default {
268268
keepLocalDesc: '保留本地配置,仅从远程添加新端点',
269269
inputFilename: '输入文件名'
270270
},
271+
update: {
272+
checkForUpdates: '检查更新',
273+
newVersionAvailable: '发现新版本',
274+
currentVersion: '当前版本',
275+
latestVersion: '最新版本',
276+
releaseDate: '发布日期',
277+
changelog: '更新内容',
278+
downloadUpdate: '下载更新',
279+
installUpdate: '安装更新',
280+
skipVersion: '跳过此版本',
281+
remindLater: '稍后提醒',
282+
downloading: '正在下载',
283+
downloadComplete: '下载完成',
284+
downloadFailed: '下载失败',
285+
installFailed: '安装失败',
286+
autoCheck: '自动检查更新',
287+
checkInterval: '检查间隔',
288+
upToDate: '已是最新版本',
289+
checkFailed: '检查更新失败',
290+
noAutoCheck: "不自动检查",
291+
everyHour: '每小时',
292+
every6Hours: '每 6 小时',
293+
everyDay: '每天',
294+
everyWeek: '每周',
295+
extractComplete: '解压完成',
296+
extractPath: '文件位置',
297+
install_instructions_windows: '1. 关闭当前 ccNexus 应用<br>2. 运行新版本的 ccNexus.exe',
298+
install_instructions_macos: '1. 关闭当前 ccNexus 应用<br>2. 将新版本的 ccNexus.app 拖到应用程序文件夹',
299+
install_instructions_linux: '1. 关闭当前 ccNexus 应用<br>2. 运行新版本的 ccNexus 可执行文件'
300+
},
271301
common: {
272302
ok: '确定',
273303
cancel: '取消',
274304
yes: '是',
275305
no: '否',
276306
confirm: '确认',
277307
delete: '删除',
278-
confirmDeleteTitle: '确认删除'
308+
confirmDeleteTitle: '确认删除',
309+
close: '关闭'
279310
},
280311
tips: [
281312
'小贴士:您可以添加多个 API 端点实现自动故障转移',

frontend/src/main.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { loadLogs, toggleLogPanel, changeLogLevel, copyLogs, clearLogs } from '.
99
import { showDataSyncDialog } from './modules/webdav.js'
1010
import { initTips } from './modules/tips.js'
1111
import { showSettingsModal, closeSettingsModal, saveSettings, applyTheme, initTheme, showAutoThemeConfigModal, closeAutoThemeConfigModal, saveAutoThemeConfig } from './modules/settings.js'
12+
import { checkUpdatesOnStartup, checkForUpdates, initUpdateSettings } from './modules/updater.js'
1213
import {
1314
showAddEndpointModal,
1415
editEndpoint,
@@ -97,6 +98,12 @@ window.addEventListener('DOMContentLoaded', async () => {
9798
showWelcomeModalIfFirstTime();
9899
showChangelogIfNewVersion();
99100

101+
// Check for updates on startup
102+
checkUpdatesOnStartup();
103+
104+
// Initialize update settings
105+
initUpdateSettings();
106+
100107
// Listen for close dialog event from backend
101108
if (window.runtime) {
102109
window.runtime.EventsOn('show-close-dialog', () => {
@@ -147,6 +154,7 @@ window.clearLogs = clearLogs;
147154
window.changeLanguage = changeLanguage;
148155
window.togglePasswordVisibility = togglePasswordVisibility;
149156
window.acceptConfirm = acceptConfirm;
157+
window.checkForUpdates = checkForUpdates;
150158
window.cancelConfirm = cancelConfirm;
151159
window.showCloseActionDialog = showCloseActionDialog;
152160
window.quitApplication = quitApplication;

frontend/src/modules/ui.js

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -547,6 +547,19 @@ export function initUI() {
547547
${t('settings.closeWindowBehaviorHelp')}
548548
</p>
549549
</div>
550+
<div class="form-group">
551+
<label>🔄 ${t('update.autoCheck')}</label>
552+
<div style="display: flex; align-items: center; gap: 12px;">
553+
<select id="check-interval" style="flex: 1;">
554+
<option value="0">${t('update.noAutoCheck')}</option>
555+
<option value="1">${t('update.everyHour')}</option>
556+
<option value="6">${t('update.every6Hours')}</option>
557+
<option value="24">${t('update.everyDay')}</option>
558+
<option value="168">${t('update.everyWeek')}</option>
559+
</select>
560+
<button id="btn-check-updates" class="btn btn-secondary" style="flex: 0 0 auto;" onclick="window.checkForUpdates()">${t('update.checkForUpdates')}</button>
561+
</div>
562+
</div>
550563
</div>
551564
<div class="modal-footer">
552565
<button class="btn btn-secondary" onclick="window.closeSettingsModal()">${t('settings.cancel')}</button>

0 commit comments

Comments
 (0)