Files
backend/internal/core/importer/modrinth.go

162 lines
5.4 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package importer
import (
"archive/zip"
"context"
"encoding/json"
"fmt"
"log"
"net/http"
"strings"
"time"
"gitea.mrixs.me/minecraft-platform/backend/internal/models"
)
// ModrinthImporter реализует импорт для сборок Modrinth (.mrpack).
type ModrinthImporter struct {
StoragePath string
HTTPClient *http.Client
}
// NewModrinthImporter создает новый экземпляр импортера.
func NewModrinthImporter(storagePath string) *ModrinthImporter {
return &ModrinthImporter{
StoragePath: storagePath,
HTTPClient: &http.Client{Timeout: 10 * time.Minute},
}
}
// downloadAndProcessFile скачивает файл и обрабатывает его.
func (i *ModrinthImporter) downloadAndProcessFile(url string) (hash string, size int64, err error) {
req, err := http.NewRequestWithContext(context.Background(), "GET", url, nil)
if err != nil {
return "", 0, err
}
resp, err := i.HTTPClient.Do(req)
if err != nil {
return "", 0, err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return "", 0, fmt.Errorf("bad status downloading file: %s", resp.Status)
}
baseImporter := SimpleZipImporter{StoragePath: i.StoragePath}
return baseImporter.processFile(resp.Body)
}
// Import реализует основной метод интерфейса ModpackImporter.
func (i *ModrinthImporter) Import(zipPath string) ([]models.ModpackFile, error) {
r, err := zip.OpenReader(zipPath)
if err != nil {
return nil, err
}
defer r.Close()
// 1. Ищем и парсим modrinth.index.json
var indexFile *zip.File
for _, f := range r.File {
if f.Name == "modrinth.index.json" {
indexFile = f
break
}
}
if indexFile == nil {
return nil, fmt.Errorf("modrinth.index.json not found in archive")
}
idxReader, err := indexFile.Open()
if err != nil {
return nil, err
}
defer idxReader.Close()
var index ModrinthIndex
if err := json.NewDecoder(idxReader).Decode(&index); err != nil {
return nil, fmt.Errorf("failed to parse modrinth.index.json: %w", err)
}
var files []models.ModpackFile
// 2. Обрабатываем файлы из индекса (скачиваем их)
for _, modFile := range index.Files {
// Пропускаем серверные файлы, которые не нужны клиенту?
// В ТЗ сказано про "клиент", но обычно лаунчер качает всё, что нужно клиенту.
// env.client != "unsupported" (обычно "required" или "optional")
if modFile.Env.Client == "unsupported" {
continue
}
if len(modFile.Downloads) == 0 {
log.Printf("Modrinth Importer: WARN - No download URLs for file '%s', skipping.", modFile.Path)
continue
}
// Используем первый доступный URL
downloadURL := modFile.Downloads[0]
log.Printf("Modrinth Importer: Downloading '%s' from %s", modFile.Path, downloadURL)
// Скачиваем файл и пересчитываем его хеш/размер (и сохраняем локально, если нужно)
// Важно: Modrinth дает хеши в индексе. Мы могли бы использовать их, но наша система
// построена на том, что мы храним файлы у себя. Поэтому нам всё равно нужно скачать их
// и сохранить в наше хранилище. Метод downloadAndProcessFile делает именно это.
hash, size, err := i.downloadAndProcessFile(downloadURL)
if err != nil {
log.Printf("Modrinth Importer: WARN - Failed to download/process '%s': %v", modFile.Path, err)
continue
}
// Сверка хеша для надежности (опционально, но полезно)
if modFile.Hashes.SHA1 != "" && modFile.Hashes.SHA1 != hash {
log.Printf("Modrinth Importer: WARN - Hash mismatch for '%s'. Index SHA1: %s, Calculated: %s", modFile.Path, modFile.Hashes.SHA1, hash)
// Можно решить: падать с ошибкой или доверять тому, что скачали.
// Пока просто предупреждаем.
}
files = append(files, models.ModpackFile{
RelativePath: modFile.Path,
FileHash: hash,
FileSize: size,
DownloadURL: downloadURL,
})
}
// 3. Обрабатываем overrides
baseImporter := SimpleZipImporter{StoragePath: i.StoragePath}
for _, f := range r.File {
// В .mrpack файлы для копирования лежат в папке "overrides/" (как правило)
// Спецификация говорит, что папка может называться иначе? Нет, обычно overrides.
// Но лучше проверить все папки, кроме системных.
// Спецификация Modrinth: "Files that are included in the modpack archive are located in the overrides directory."
prefix := "overrides/"
if strings.HasPrefix(f.Name, prefix) && !f.FileInfo().IsDir() {
fileReader, err := f.Open()
if err != nil {
return nil, err
}
hash, size, err := baseImporter.processFile(fileReader)
if err != nil {
fileReader.Close()
return nil, err
}
fileReader.Close()
relativePath := strings.TrimPrefix(f.Name, prefix)
files = append(files, models.ModpackFile{
RelativePath: relativePath,
FileHash: hash,
FileSize: size,
})
}
}
return files, nil
}