Add server components
build-winui / winui (push) Has been cancelled

This commit is contained in:
QWQLwToo
2026-06-26 13:28:09 +08:00
parent 7ecc6a8923
commit 079ee4eaeb
168 changed files with 37475 additions and 0 deletions
@@ -0,0 +1,259 @@
package releases
import (
"crypto/sha256"
"encoding/hex"
"encoding/json"
"io"
"net/http"
"os"
"path/filepath"
"regexp"
"sort"
"strconv"
"strings"
"time"
"ymhut-box/server/unified-management/internal/config"
"ymhut-box/server/unified-management/internal/db"
"ymhut-box/server/unified-management/internal/notices"
)
type Service struct {
cfg *config.Config
store *db.Store
notices *notices.Service
}
type Package struct {
ID string `json:"id"`
Name string `json:"name"`
Version string `json:"version"`
Platform string `json:"platform"`
Arch string `json:"arch"`
URL string `json:"url"`
SHA256 string `json:"sha256"`
Size int64 `json:"size"`
Required bool `json:"required"`
Enabled bool `json:"enabled"`
FileName string `json:"fileName"`
UpdatedAt string `json:"updatedAt"`
}
func NewService(cfg *config.Config, store *db.Store, noticeService ...*notices.Service) *Service {
service := &Service{cfg: cfg, store: store}
if len(noticeService) > 0 {
service.notices = noticeService[0]
}
return service
}
func (s *Service) LegacyUpdateInfo(r *http.Request) map[string]any {
payload := readJSON(filepath.Join(s.cfg.UpdatePublicDir, "update-info.json"))
manifest := s.Manifest(r)
for _, key := range []string{"app_version", "download_url", "download_mirrors", "detected_product", "detected_packages", "packages", "modules", "manifest_version", "release_notes", "release_notes_md", "message", "message_md", "notices", "latest_notice"} {
if value, ok := manifest[key]; ok {
payload[key] = value
}
}
return payload
}
func (s *Service) Manifest(r *http.Request) map[string]any {
payload := readJSON(filepath.Join(s.cfg.UpdatePublicDir, "update-info.json"))
packages := s.ScanPackages(r)
modules := readJSON(filepath.Join(s.cfg.UpdatePublicDir, "modules.json"))["modules"]
if modules == nil {
modules = []any{}
}
payload["manifest_version"] = 2
payload["service_version"] = config.Version
payload["packages"] = packages
payload["modules"] = modules
payload["assets"] = []any{}
payload["generated_at"] = time.Now().UTC().Format(time.RFC3339)
if s.notices != nil {
if items, err := s.notices.List(50); err == nil && len(items) > 0 {
publicNotices := notices.PublicList(items)
payload["notices"] = publicNotices
payload["latest_notice"] = publicNotices[0]
latestNotice := items[0]
setIfMissing(payload, "app_version", latestNotice.Version)
setIfMissing(payload, "title", latestNotice.Title)
setIfMissing(payload, "message", latestNotice.Message)
setIfMissing(payload, "message_md", latestNotice.MessageMD)
setIfMissing(payload, "release_notes", latestNotice.ReleaseNotes)
setIfMissing(payload, "release_notes_md", latestNotice.ReleaseNotesMD)
setIfMissing(payload, "download_url", latestNotice.DownloadURL)
}
}
if len(packages) > 0 {
latest := packages[0]
payload["app_version"] = latest.Version
payload["download_url"] = latest.URL
payload["download_mirrors"] = []map[string]any{{
"id": "primary",
"name": "官方直连",
"url": latest.URL,
"type": "direct",
"sha256": latest.SHA256,
"enabled": true,
}}
payload["detected_product"] = latest.Name
}
return payload
}
func setIfMissing(payload map[string]any, key, value string) {
if strings.TrimSpace(value) == "" {
return
}
if existing, ok := payload[key].(string); !ok || strings.TrimSpace(existing) == "" {
payload[key] = value
}
}
func (s *Service) ScanPackages(r *http.Request) []Package {
entries, err := os.ReadDir(s.cfg.DownloadsDir)
if err != nil {
return []Package{}
}
base := requestBaseURL(r, s.cfg.BaseURL)
items := []Package{}
for _, entry := range entries {
if entry.IsDir() {
continue
}
name := entry.Name()
lower := strings.ToLower(name)
if !(strings.HasSuffix(lower, ".exe") || strings.HasSuffix(lower, ".msix") || strings.HasSuffix(lower, ".appinstaller") || strings.HasSuffix(lower, ".msi")) {
continue
}
info, err := entry.Info()
if err != nil {
continue
}
version := detectVersion(name)
platform, arch := detectPlatform(name)
product := detectProduct(name)
url := base + "/downloads/" + name
items = append(items, Package{
ID: strings.ToLower(strings.ReplaceAll(product+"-"+platform+"-"+arch+"-"+version, " ", "-")),
Name: product,
Version: version,
Platform: platform,
Arch: arch,
URL: url,
SHA256: sha256File(filepath.Join(s.cfg.DownloadsDir, name)),
Size: info.Size(),
Required: strings.Contains(strings.ToLower(product), "ymhut"),
Enabled: true,
FileName: name,
UpdatedAt: info.ModTime().UTC().Format(time.RFC3339),
})
}
sort.Slice(items, func(i, j int) bool {
return compareVersion(items[i].Version, items[j].Version) > 0
})
return items
}
func (s *Service) StaticJSON(name string) map[string]any {
return readJSON(filepath.Join(s.cfg.UpdatePublicDir, name))
}
func readJSON(path string) map[string]any {
data, err := os.ReadFile(path)
if err != nil {
return map[string]any{}
}
var payload map[string]any
if err := json.Unmarshal(data, &payload); err != nil {
return map[string]any{}
}
return payload
}
func requestBaseURL(r *http.Request, fallback string) string {
if r != nil {
scheme := r.Header.Get("X-Forwarded-Proto")
if scheme == "" {
if r.TLS != nil {
scheme = "https"
} else {
scheme = "http"
}
}
if r.Host != "" {
return scheme + "://" + r.Host
}
}
return strings.TrimRight(fallback, "/")
}
var versionPattern = regexp.MustCompile(`\d+\.\d+\.\d+(?:\.\d+)?`)
func detectVersion(name string) string {
match := versionPattern.FindString(name)
if match == "" {
return "0.0.0"
}
return match
}
func detectPlatform(name string) (string, string) {
lower := strings.ToLower(name)
platform := "windows"
if strings.Contains(lower, "appinstaller") || strings.HasSuffix(lower, ".msix") || strings.HasSuffix(lower, ".exe") || strings.HasSuffix(lower, ".msi") {
platform = "windows"
}
arch := "x64"
if strings.Contains(lower, "arm64") {
arch = "arm64"
} else if strings.Contains(lower, "x86") && !strings.Contains(lower, "x64") {
arch = "x86"
}
return platform, arch
}
func detectProduct(name string) string {
if strings.Contains(strings.ToLower(name), "ymhut") {
return "YMhut Box"
}
return "YMhut Package"
}
func compareVersion(a, b string) int {
as := strings.Split(a, ".")
bs := strings.Split(b, ".")
for len(as) < 4 {
as = append(as, "0")
}
for len(bs) < 4 {
bs = append(bs, "0")
}
for i := 0; i < 4; i++ {
ai, _ := strconv.Atoi(as[i])
bi, _ := strconv.Atoi(bs[i])
if ai > bi {
return 1
}
if ai < bi {
return -1
}
}
return 0
}
func sha256File(path string) string {
file, err := os.Open(path)
if err != nil {
return ""
}
defer file.Close()
hash := sha256.New()
if _, err := io.Copy(hash, file); err != nil {
return ""
}
return hex.EncodeToString(hash.Sum(nil))
}