package download import ( "archive/zip" "crypto/sha256" "encoding/hex" "fmt" "io" "os" "path/filepath" "regexp" "strings" ) var versionRe = regexp.MustCompile(`v\d+[\.\d]*`) type Service struct { repo *Repository gameDir string } func NewService(repo *Repository, gameDir string) *Service { return &Service{repo: repo, gameDir: gameDir} } func (s *Service) GetInfo() (*Info, error) { return s.repo.GetLatest() } func (s *Service) GameFilePath() string { return filepath.Join(s.gameDir, "game.zip") } // Upload streams the body directly to disk, then extracts metadata from the zip. func (s *Service) Upload(filename string, body io.Reader, baseURL string) (*Info, error) { if err := os.MkdirAll(s.gameDir, 0755); err != nil { return nil, fmt.Errorf("디렉토리 생성 실패: %w", err) } finalPath := s.GameFilePath() tmpPath := finalPath + ".tmp" f, err := os.Create(tmpPath) if err != nil { return nil, fmt.Errorf("파일 생성 실패: %w", err) } n, err := io.Copy(f, body) f.Close() if err != nil { os.Remove(tmpPath) return nil, fmt.Errorf("파일 저장 실패: %w", err) } if err := os.Rename(tmpPath, finalPath); err != nil { os.Remove(tmpPath) return nil, fmt.Errorf("파일 이동 실패: %w", err) } version := "" if m := versionRe.FindString(filename); m != "" { version = m } fileSize := "" if n > 0 { mb := float64(n) / 1024 / 1024 if mb >= 1000 { fileSize = fmt.Sprintf("%.1f GB", mb/1024) } else { fileSize = fmt.Sprintf("%.1f MB", mb) } } fileHash := hashGameExeFromZip(finalPath) info, err := s.repo.GetLatest() if err != nil { info = &Info{} } info.URL = baseURL + "/api/download/file" info.Version = version info.FileName = filename info.FileSize = fileSize info.FileHash = fileHash return info, s.repo.Save(info) } func hashGameExeFromZip(zipPath string) string { r, err := zip.OpenReader(zipPath) if err != nil { return "" } defer r.Close() for _, f := range r.File { if strings.EqualFold(filepath.Base(f.Name), "A301.exe") { rc, err := f.Open() if err != nil { return "" } h := sha256.New() io.Copy(h, rc) rc.Close() return hex.EncodeToString(h.Sum(nil)) } } return "" }