diff --git a/frontend/package.json.md5 b/frontend/package.json.md5 index fa8a8b2..848588e 100755 --- a/frontend/package.json.md5 +++ b/frontend/package.json.md5 @@ -1 +1 @@ -571d014306268cf67665967059cda912 \ No newline at end of file +26a843d5fd071d0c7e9d8022e98eb4e3 \ No newline at end of file diff --git a/frontend/wailsjs/runtime/runtime.d.ts b/frontend/wailsjs/runtime/runtime.d.ts index 3bbea84..4445dac 100644 --- a/frontend/wailsjs/runtime/runtime.d.ts +++ b/frontend/wailsjs/runtime/runtime.d.ts @@ -246,85 +246,4 @@ export function OnFileDropOff() :void export function CanResolveFilePaths(): boolean; // Resolves file paths for an array of files -export function ResolveFilePaths(files: File[]): void - -// Notification types -export interface NotificationOptions { - id: string; - title: string; - subtitle?: string; // macOS and Linux only - body?: string; - categoryId?: string; - data?: { [key: string]: any }; -} - -export interface NotificationAction { - id?: string; - title?: string; - destructive?: boolean; // macOS-specific -} - -export interface NotificationCategory { - id?: string; - actions?: NotificationAction[]; - hasReplyField?: boolean; - replyPlaceholder?: string; - replyButtonTitle?: string; -} - -// [InitializeNotifications](https://wails.io/docs/reference/runtime/notification#initializenotifications) -// Initializes the notification service for the application. -// This must be called before sending any notifications. -export function InitializeNotifications(): Promise; - -// [CleanupNotifications](https://wails.io/docs/reference/runtime/notification#cleanupnotifications) -// Cleans up notification resources and releases any held connections. -export function CleanupNotifications(): Promise; - -// [IsNotificationAvailable](https://wails.io/docs/reference/runtime/notification#isnotificationavailable) -// Checks if notifications are available on the current platform. -export function IsNotificationAvailable(): Promise; - -// [RequestNotificationAuthorization](https://wails.io/docs/reference/runtime/notification#requestnotificationauthorization) -// Requests notification authorization from the user (macOS only). -export function RequestNotificationAuthorization(): Promise; - -// [CheckNotificationAuthorization](https://wails.io/docs/reference/runtime/notification#checknotificationauthorization) -// Checks the current notification authorization status (macOS only). -export function CheckNotificationAuthorization(): Promise; - -// [SendNotification](https://wails.io/docs/reference/runtime/notification#sendnotification) -// Sends a basic notification with the given options. -export function SendNotification(options: NotificationOptions): Promise; - -// [SendNotificationWithActions](https://wails.io/docs/reference/runtime/notification#sendnotificationwithactions) -// Sends a notification with action buttons. Requires a registered category. -export function SendNotificationWithActions(options: NotificationOptions): Promise; - -// [RegisterNotificationCategory](https://wails.io/docs/reference/runtime/notification#registernotificationcategory) -// Registers a notification category that can be used with SendNotificationWithActions. -export function RegisterNotificationCategory(category: NotificationCategory): Promise; - -// [RemoveNotificationCategory](https://wails.io/docs/reference/runtime/notification#removenotificationcategory) -// Removes a previously registered notification category. -export function RemoveNotificationCategory(categoryId: string): Promise; - -// [RemoveAllPendingNotifications](https://wails.io/docs/reference/runtime/notification#removeallpendingnotifications) -// Removes all pending notifications from the notification center. -export function RemoveAllPendingNotifications(): Promise; - -// [RemovePendingNotification](https://wails.io/docs/reference/runtime/notification#removependingnotification) -// Removes a specific pending notification by its identifier. -export function RemovePendingNotification(identifier: string): Promise; - -// [RemoveAllDeliveredNotifications](https://wails.io/docs/reference/runtime/notification#removealldeliverednotifications) -// Removes all delivered notifications from the notification center. -export function RemoveAllDeliveredNotifications(): Promise; - -// [RemoveDeliveredNotification](https://wails.io/docs/reference/runtime/notification#removedeliverednotification) -// Removes a specific delivered notification by its identifier. -export function RemoveDeliveredNotification(identifier: string): Promise; - -// [RemoveNotification](https://wails.io/docs/reference/runtime/notification#removenotification) -// Removes a notification by its identifier (cross-platform convenience function). -export function RemoveNotification(identifier: string): Promise; \ No newline at end of file +export function ResolveFilePaths(files: File[]): void \ No newline at end of file diff --git a/frontend/wailsjs/runtime/runtime.js b/frontend/wailsjs/runtime/runtime.js index 556621e..7cb89d7 100644 --- a/frontend/wailsjs/runtime/runtime.js +++ b/frontend/wailsjs/runtime/runtime.js @@ -239,60 +239,4 @@ export function CanResolveFilePaths() { export function ResolveFilePaths(files) { return window.runtime.ResolveFilePaths(files); -} - -export function InitializeNotifications() { - return window.runtime.InitializeNotifications(); -} - -export function CleanupNotifications() { - return window.runtime.CleanupNotifications(); -} - -export function IsNotificationAvailable() { - return window.runtime.IsNotificationAvailable(); -} - -export function RequestNotificationAuthorization() { - return window.runtime.RequestNotificationAuthorization(); -} - -export function CheckNotificationAuthorization() { - return window.runtime.CheckNotificationAuthorization(); -} - -export function SendNotification(options) { - return window.runtime.SendNotification(options); -} - -export function SendNotificationWithActions(options) { - return window.runtime.SendNotificationWithActions(options); -} - -export function RegisterNotificationCategory(category) { - return window.runtime.RegisterNotificationCategory(category); -} - -export function RemoveNotificationCategory(categoryId) { - return window.runtime.RemoveNotificationCategory(categoryId); -} - -export function RemoveAllPendingNotifications() { - return window.runtime.RemoveAllPendingNotifications(); -} - -export function RemovePendingNotification(identifier) { - return window.runtime.RemovePendingNotification(identifier); -} - -export function RemoveAllDeliveredNotifications() { - return window.runtime.RemoveAllDeliveredNotifications(); -} - -export function RemoveDeliveredNotification(identifier) { - return window.runtime.RemoveDeliveredNotification(identifier); -} - -export function RemoveNotification(identifier) { - return window.runtime.RemoveNotification(identifier); } \ No newline at end of file diff --git a/go.mod b/go.mod index f195fd3..29edc5a 100644 --- a/go.mod +++ b/go.mod @@ -15,7 +15,7 @@ require ( github.com/redis/go-redis/v9 v9.17.3 github.com/sijms/go-ora/v2 v2.9.0 github.com/taosdata/driver-go/v3 v3.7.8 - github.com/wailsapp/wails/v2 v2.12.0 + github.com/wailsapp/wails/v2 v2.11.0 github.com/xuri/excelize/v2 v2.10.0 go.mongodb.org/mongo-driver v1.17.9 go.mongodb.org/mongo-driver/v2 v2.5.0 @@ -27,7 +27,6 @@ require ( ) require ( - git.sr.ht/~jackmordaunt/go-toast/v2 v2.0.3 // indirect github.com/kr/pretty v0.3.1 // indirect github.com/rogpeppe/go-internal v1.12.0 // indirect gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect diff --git a/go.sum b/go.sum index cbf67a4..a93b158 100644 --- a/go.sum +++ b/go.sum @@ -1,7 +1,5 @@ filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= -git.sr.ht/~jackmordaunt/go-toast/v2 v2.0.3 h1:N3IGoHHp9pb6mj1cbXbuaSXV/UMKwmbKLf53nQmtqMA= -git.sr.ht/~jackmordaunt/go-toast/v2 v2.0.3/go.mod h1:QtOLZGz8olr4qH2vWK0QH0w0O4T9fEIjMuWpKUsH7nc= gitea.com/kingbase/gokb v0.0.0-20201021123113-29bd62a876c3 h1:QjslQNaH5Nuap5i4nijS0OYV6GMk5kqrAmgU90zBKd4= gitea.com/kingbase/gokb v0.0.0-20201021123113-29bd62a876c3/go.mod h1:7lH5A1jzCXD9Nl16DzaBUOfDAT8NPrDmZwKu1p5wf94= gitee.com/chunanyong/dm v1.8.22 h1:H7fsrnUIvEA0jlDWew7vwELry1ff+tLMIu2Fk2cIBSg= @@ -245,8 +243,8 @@ github.com/wailsapp/go-webview2 v1.0.22 h1:YT61F5lj+GGaat5OB96Aa3b4QA+mybD0Ggq6N github.com/wailsapp/go-webview2 v1.0.22/go.mod h1:qJmWAmAmaniuKGZPWwne+uor3AHMB5PFhqiK0Bbj8kc= github.com/wailsapp/mimetype v1.4.1 h1:pQN9ycO7uo4vsUUuPeHEYoUkLVkaRntMnHJxVwYhwHs= github.com/wailsapp/mimetype v1.4.1/go.mod h1:9aV5k31bBOv5z6u+QP8TltzvNGJPmNJD4XlAL3U+j3o= -github.com/wailsapp/wails/v2 v2.12.0 h1:BHO/kLNWFHYjCzucxbzAYZWUjub1Tvb4cSguQozHn5c= -github.com/wailsapp/wails/v2 v2.12.0/go.mod h1:mo1bzK1DEJrobt7YrBjgxvb5Sihb1mhAY09hppbibQg= +github.com/wailsapp/wails/v2 v2.11.0 h1:seLacV8pqupq32IjS4Y7V8ucab0WZwtK6VvUVxSBtqQ= +github.com/wailsapp/wails/v2 v2.11.0/go.mod h1:jrf0ZaM6+GBc1wRmXsM8cIvzlg0karYin3erahI4+0k= github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c= github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI= github.com/xdg-go/scram v1.1.1/go.mod h1:RaEWvsqvNKKvBPvcKeFjrG2cJqOkHTiyTpzz23ni57g= diff --git a/internal/app/methods_driver.go b/internal/app/methods_driver.go index a7090b9..4802bf0 100644 --- a/internal/app/methods_driver.go +++ b/internal/app/methods_driver.go @@ -1373,6 +1373,10 @@ func effectiveDriverEngine(definition driverDefinition) string { } func resolveDriverDefinition(driverType string) (driverDefinition, bool) { + effectivePackages, err := resolveEffectiveDriverPackages("") + if err == nil { + return resolveDriverDefinitionWithPackages(driverType, effectivePackages) + } return resolveDriverDefinitionWithPackages(driverType, nil) } @@ -2313,7 +2317,7 @@ func inferDriverInstallVersionByDownloadURL(downloadURL string) string { if err == nil && parsed != nil { switch strings.ToLower(strings.TrimSpace(parsed.Scheme)) { case "builtin": - return "go-embedded" + return "" case "local": return "local" case "http", "https": @@ -3060,6 +3064,54 @@ func installOptionalDriverAgentFromLocalZip(zipPath string, definition driverDef return filepath.ToSlash(strings.TrimPrefix(strings.TrimSpace(entry.Name), "./")), nil } +func buildOptionalDriverInstallPlanMessage(displayName string, selectedVersion string, forceSourceBuild bool, preferSourceBuildBeforeDownload bool, restrictToExplicitArtifact bool, directURLCount int, bundleURLCount int) string { + name := strings.TrimSpace(displayName) + if name == "" { + name = "驱动" + } + versionText := normalizeVersion(strings.TrimSpace(selectedVersion)) + if versionText == "" { + versionText = "未标注版本" + } + + if forceSourceBuild { + return fmt.Sprintf("准备安装 %s 驱动代理(版本 %s);当前版本仅允许本地源码构建", name, versionText) + } + if preferSourceBuildBeforeDownload { + return fmt.Sprintf("准备安装 %s 驱动代理(版本 %s);先尝试本地源码构建,失败后继续下载兜底", name, versionText) + } + if directURLCount > 0 && !restrictToExplicitArtifact && bundleURLCount > 0 { + return fmt.Sprintf("准备安装 %s 驱动代理(版本 %s);先尝试 %d 个预编译直链,失败后转入 %d 个驱动总包源", name, versionText, directURLCount, bundleURLCount) + } + if directURLCount > 0 && restrictToExplicitArtifact { + return fmt.Sprintf("准备安装 %s 驱动代理(版本 %s);仅允许显式版本资产,先尝试 %d 个预编译直链", name, versionText, directURLCount) + } + if directURLCount > 0 { + return fmt.Sprintf("准备安装 %s 驱动代理(版本 %s);先尝试 %d 个预编译直链", name, versionText, directURLCount) + } + if !restrictToExplicitArtifact && bundleURLCount > 0 { + return fmt.Sprintf("准备安装 %s 驱动代理(版本 %s);未提供预编译直链,直接尝试 %d 个驱动总包源", name, versionText, bundleURLCount) + } + return fmt.Sprintf("准备安装 %s 驱动代理(版本 %s);未命中发布资产时将回退到本地源码构建", name, versionText) +} + +func buildOptionalDriverFallbackProgressMessage(displayName string, directURLCount int, bundleURLCount int, restrictToExplicitArtifact bool) string { + name := strings.TrimSpace(displayName) + if name == "" { + name = "驱动" + } + if directURLCount > 0 && !restrictToExplicitArtifact && bundleURLCount > 0 { + return fmt.Sprintf("预编译直链未命中,转入驱动总包兜底(%s,剩余 %d 个总包源)", name, bundleURLCount) + } + if directURLCount > 0 && restrictToExplicitArtifact { + return fmt.Sprintf("预编译直链未命中;当前版本仅允许显式资产,跳过驱动总包(%s)", name) + } + if !restrictToExplicitArtifact && bundleURLCount > 0 { + return fmt.Sprintf("直链不可用,转入驱动总包兜底(%s,剩余 %d 个总包源)", name, bundleURLCount) + } + return fmt.Sprintf("发布资产未命中,准备回退到本地源码构建(%s)", name) +} + func ensureOptionalDriverAgentBinary(a *App, definition driverDefinition, executablePath string, downloadURL string, selectedVersion string) (string, string, error) { driverType := normalizeDriverType(definition.Type) displayName := resolveDriverDisplayName(definition) @@ -3067,6 +3119,16 @@ func ensureOptionalDriverAgentBinary(a *App, definition driverDefinition, execut preferSourceBuildBeforeDownload := shouldPreferSourceBuildBeforeDownload(driverType, selectedVersion) skipReuseCandidate := shouldSkipReusableAgentCandidate(driverType, selectedVersion) restrictToExplicitArtifact := shouldRestrictToExplicitVersionArtifact(definition, selectedVersion) + downloadURLs := []string{} + bundleURLs := []string{} + if !forceSourceBuild { + downloadURLs = resolveOptionalDriverAgentDownloadURLs(definition, downloadURL, selectedVersion) + if !restrictToExplicitArtifact { + bundleURLs = resolveOptionalDriverBundleDownloadURLs() + } + } + planMessage := buildOptionalDriverInstallPlanMessage(displayName, selectedVersion, forceSourceBuild, preferSourceBuildBeforeDownload, restrictToExplicitArtifact, len(downloadURLs), len(bundleURLs)) + logger.Infof("%s,driver=%s version=%s direct_candidates=%d bundle_candidates=%d force_source_build=%v restrict_explicit=%v prefer_source_first=%v", planMessage, driverType, normalizeVersion(selectedVersion), len(downloadURLs), len(bundleURLs), forceSourceBuild, restrictToExplicitArtifact, preferSourceBuildBeforeDownload) info, err := os.Stat(executablePath) if err == nil && !info.IsDir() { @@ -3087,7 +3149,7 @@ func ensureOptionalDriverAgentBinary(a *App, definition driverDefinition, execut return "", "", fmt.Errorf("创建 %s 驱动目录失败:%w", displayName, mkErr) } if a != nil { - a.emitDriverDownloadProgress(driverType, "downloading", 10, 100, "检查本地驱动代理缓存") + a.emitDriverDownloadProgress(driverType, "downloading", 10, 100, planMessage) } if !skipReuseCandidate { if sourcePath, ok := findExistingOptionalDriverAgentCandidate(definition, executablePath); ok { @@ -3124,7 +3186,6 @@ func ensureOptionalDriverAgentBinary(a *App, definition driverDefinition, execut } if !forceSourceBuild { - downloadURLs := resolveOptionalDriverAgentDownloadURLs(definition, downloadURL, selectedVersion) if len(downloadURLs) > 0 { for _, candidateURL := range downloadURLs { if a != nil { @@ -3134,11 +3195,16 @@ func ensureOptionalDriverAgentBinary(a *App, definition driverDefinition, execut if dlErr == nil { return candidateURL, hash, nil } + logger.Warnf("下载预编译 %s 驱动代理失败,url=%s err=%v", displayName, candidateURL, dlErr) downloadErrs = append(downloadErrs, fmt.Sprintf("%s: %s", candidateURL, strings.TrimSpace(dlErr.Error()))) } } - bundleURLs := resolveOptionalDriverBundleDownloadURLs() - if !restrictToExplicitArtifact && len(bundleURLs) > 0 { + if len(bundleURLs) > 0 { + fallbackMessage := buildOptionalDriverFallbackProgressMessage(displayName, len(downloadURLs), len(bundleURLs), restrictToExplicitArtifact) + logger.Infof("%s,driver=%s version=%s", fallbackMessage, driverType, normalizeVersion(selectedVersion)) + if a != nil { + a.emitDriverDownloadProgress(driverType, "downloading", 20, 100, fallbackMessage) + } for _, bundleURL := range bundleURLs { if a != nil { a.emitDriverDownloadProgress(driverType, "downloading", 20, 100, fmt.Sprintf("从驱动总包提取 %s 代理", displayName)) @@ -3147,8 +3213,15 @@ func ensureOptionalDriverAgentBinary(a *App, definition driverDefinition, execut if bundleErr == nil { return source, hash, nil } + logger.Warnf("从驱动总包提取 %s 驱动代理失败,url=%s err=%v", displayName, bundleURL, bundleErr) downloadErrs = append(downloadErrs, fmt.Sprintf("%s: %s", bundleURL, strings.TrimSpace(bundleErr.Error()))) } + } else if len(downloadURLs) > 0 || restrictToExplicitArtifact { + fallbackMessage := buildOptionalDriverFallbackProgressMessage(displayName, len(downloadURLs), 0, restrictToExplicitArtifact) + logger.Infof("%s,driver=%s version=%s", fallbackMessage, driverType, normalizeVersion(selectedVersion)) + if a != nil { + a.emitDriverDownloadProgress(driverType, "downloading", 20, 100, fallbackMessage) + } } } if a != nil { @@ -3401,9 +3474,6 @@ func shouldForceSourceBuildForResolvedDownload(driverType string, selectedVersio func shouldPreferSourceBuildBeforeDownload(driverType string, selectedVersion string) bool { _ = selectedVersion switch normalizeDriverType(driverType) { - case "kingbase": - // 金仓迭代期优先本地源码构建,避免下载到旧版本预编译代理导致修复不生效。 - return true default: return false } diff --git a/internal/app/methods_driver_version_test.go b/internal/app/methods_driver_version_test.go index 09c155b..e69cac2 100644 --- a/internal/app/methods_driver_version_test.go +++ b/internal/app/methods_driver_version_test.go @@ -150,6 +150,54 @@ func TestResolveOptionalDriverAgentDownloadURLsSkipsBundleOnlyDamengAsset(t *tes } } +func TestResolveDriverInstallVersionUsesPinnedVersionForBuiltinActivateURL(t *testing.T) { + definition, ok := resolveDriverDefinition("sqlserver") + if !ok { + t.Fatal("expected sqlserver driver definition") + } + if normalizeVersion(definition.PinnedVersion) == "" { + t.Fatal("expected sqlserver default definition to include builtin manifest pinned version") + } + + got := resolveDriverInstallVersion("", "builtin://activate/sqlserver", definition) + want := normalizeVersion(definition.PinnedVersion) + if got != want { + t.Fatalf("expected builtin activate URL to fall back to pinned version %q, got %q", want, got) + } +} + +func TestBuiltinActivatePinnedVersionDoesNotRestrictBundleFallback(t *testing.T) { + definition, ok := resolveDriverDefinition("sqlserver") + if !ok { + t.Fatal("expected sqlserver driver definition") + } + + selectedVersion := resolveDriverInstallVersion("", "builtin://activate/sqlserver", definition) + if shouldRestrictToExplicitVersionArtifact(definition, selectedVersion) { + t.Fatalf("expected builtin activate default version %q not to restrict bundle fallback", selectedVersion) + } +} + +func TestBuildOptionalDriverInstallPlanMessagePrefersDirectThenBundle(t *testing.T) { + message := buildOptionalDriverInstallPlanMessage("SQL Server", "1.9.6", false, false, false, 1, 2) + if !strings.Contains(message, "先尝试 1 个预编译直链") { + t.Fatalf("expected direct-download hint, got %q", message) + } + if !strings.Contains(message, "失败后转入 2 个驱动总包源") { + t.Fatalf("expected bundle fallback hint, got %q", message) + } +} + +func TestBuildOptionalDriverFallbackProgressMessageReportsBundleFallback(t *testing.T) { + message := buildOptionalDriverFallbackProgressMessage("SQL Server", 1, 2, false) + if !strings.Contains(message, "预编译直链未命中") { + t.Fatalf("expected direct miss hint, got %q", message) + } + if !strings.Contains(message, "转入驱动总包兜底") { + t.Fatalf("expected bundle fallback hint, got %q", message) + } +} + func TestDownloadDriverPackageRejectsUnsupportedMongoVersion(t *testing.T) { app := &App{} @@ -247,6 +295,36 @@ func TestShouldForceSourceBuildForResolvedDownload(t *testing.T) { } } +func TestShouldPreferSourceBuildBeforeDownloadDoesNotPreferKingbase(t *testing.T) { + if shouldPreferSourceBuildBeforeDownload("kingbase", "0.0.0-20201021123113-29bd62a876c3") { + t.Fatal("expected kingbase release install not to prefer source build before download") + } +} + +func TestResolveOptionalDriverAgentDownloadURLsIncludesPublishedKingbaseAsset(t *testing.T) { + definition, ok := resolveDriverDefinition("kingbase") + if !ok { + t.Fatal("expected kingbase driver definition") + } + + version := normalizeVersion(definition.PinnedVersion) + assetName := optionalDriverReleaseAssetNameForVersion("kingbase", version) + publishedAssets := map[string]int64{ + assetName: 18 << 20, + } + seedReleaseAssetCacheEntry(t, "tag:v"+version, publishedAssets, publishedAssets) + seedReleaseAssetCacheEntry(t, "latest", publishedAssets, publishedAssets) + + urls := resolveOptionalDriverAgentDownloadURLs(definition, "builtin://activate/kingbase", version) + if len(urls) == 0 { + t.Fatal("expected kingbase pinned install to include published download candidates") + } + + if !strings.Contains(urls[0], assetName) { + t.Fatalf("expected first kingbase download URL to contain %q, got %q", assetName, urls[0]) + } +} + func TestInstallOptionalDriverAgentFromLocalPathSupportsMongoV1DirectoryImport(t *testing.T) { definition, ok := resolveDriverDefinition("mongodb") if !ok {