conjure-os/lib/provider/provider.go
2025-07-19 19:58:57 +00:00

268 lines
6.1 KiB
Go

package provider
import (
"archive/zip"
"conjure-os/lib/config"
"conjure-os/lib/models"
"encoding/json"
"fmt"
"io"
"log"
"net/http"
"net/url"
"os"
"path/filepath"
"strings"
"gopkg.in/yaml.v3"
)
var token string
func Update() {
//requestURL := fmt.Sprintf("http://localhost:%d", 8080)
//login(requestURL)
//allGames(requestURL)
}
type AuthResponse struct {
Token string `json:"token"`
}
func login(requestURL string) {
client := &http.Client{}
form := url.Values{
"username": {"test"},
"password": {"test"}}
req, _ := http.NewRequest("POST", requestURL+"/login", strings.NewReader(form.Encode()))
req.Header.Set("API-Version", "1")
req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
res, err := client.Do(req)
check(err)
var auth AuthResponse
err = json.NewDecoder(res.Body).Decode(&auth)
check(err)
token = auth.Token
}
func allGames(requestURL string) {
client := &http.Client{}
req, _ := http.NewRequest("GET", requestURL+"/games", nil)
req.Header.Set("Authorization", token)
req.Header.Set("API-Version", "1")
res, err := client.Do(req)
check(err)
fmt.Printf("client: status code: %d\n", res.StatusCode)
}
func GetOrSetEnvKey(key string, defaultValue string) string {
value, exist := os.LookupEnv(key)
if exist {
return value
}
err := os.Setenv(key, defaultValue)
if err != nil {
log.Println(err)
}
return defaultValue
}
func extractZipToSiblingFolder(zipPath string) error {
// Determine destination folder name (same name as zip file, without .zip)
zipBase := strings.TrimSuffix(filepath.Base(zipPath), ".conj")
zipBase = strings.TrimSuffix(filepath.Base(zipBase), ".zip")
destDir := filepath.Join(filepath.Dir(zipPath), zipBase)
// Delete destination folder if it exists
if _, err := os.Stat(destDir); err == nil {
err = os.RemoveAll(destDir)
if err != nil {
return fmt.Errorf("failed to remove existing folder: %v", err)
}
}
// Open the zip archive
r, err := zip.OpenReader(zipPath)
if err != nil {
return err
}
defer r.Close()
// Create the destination directory
err = os.MkdirAll(destDir, os.ModePerm)
if err != nil {
return err
}
for _, f := range r.File {
destPath := filepath.Join(destDir, f.Name)
// ZipSlip protection
if !strings.HasPrefix(destPath, filepath.Clean(destDir)+string(os.PathSeparator)) {
return fmt.Errorf("illegal file path: %s", destPath)
}
if f.FileInfo().IsDir() {
if err := os.MkdirAll(destPath, os.ModePerm); err != nil {
return err
}
continue
}
// Ensure parent directories exist
if err := os.MkdirAll(filepath.Dir(destPath), os.ModePerm); err != nil {
return err
}
// Create and copy the file
srcFile, err := f.Open()
if err != nil {
return err
}
defer srcFile.Close()
dstFile, err := os.OpenFile(destPath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode())
if err != nil {
return err
}
defer dstFile.Close()
if _, err := io.Copy(dstFile, srcFile); err != nil {
return err
}
}
return nil
}
func extractZipsInFolder(folder string) error {
entries, err := os.ReadDir(folder)
if err != nil {
return fmt.Errorf("reading directory failed: %w", err)
}
for _, entry := range entries {
if entry.IsDir() {
continue
}
if strings.HasSuffix(strings.ToLower(entry.Name()), ".zip") {
zipPath := filepath.Join(folder, entry.Name())
fmt.Println("Extracting:", zipPath)
if err := extractZipToSiblingFolder(zipPath); err != nil {
return fmt.Errorf("failed to extract %s: %w", zipPath, err)
}
}
}
return nil
}
func ExtractGame(game models.Metadata) string {
info, err := os.Stat(filepath.Join(config.GetDefaultConjureGamesDirectory(), game.Id))
if err != nil || !info.IsDir() {
gamePath := filepath.Join(config.GetDefaultConjureGamesDirectory(), fmt.Sprintf("%s.conj", game.Id))
err = extractZipToSiblingFolder(gamePath)
check(err)
}
gamePath := filepath.Join(config.GetDefaultConjureGamesDirectory(), game.Id)
_, err = os.Stat(filepath.Join(gamePath, game.Files))
if err == nil {
return filepath.Join(gamePath, game.Files)
}
err = extractZipsInFolder(gamePath)
check(err)
gamePath = filepath.Join(gamePath, game.Files)
return gamePath
}
func GetConjureGameInfo() []models.Metadata {
gamePath := config.GetDefaultConjureGamesDirectory()
fmt.Println("Loading games....")
fmt.Println(gamePath)
entries, err := os.ReadDir(gamePath)
if err != nil {
log.Fatal(err)
}
var games []models.Metadata
for _, e := range entries {
if e.IsDir() {
continue
} else if filepath.Ext(e.Name()) == ".conj" {
conjPath := filepath.Join(gamePath, e.Name())
conjBase := strings.TrimSuffix(conjPath, ".conj")
// Check if the destination folder already exists
if _, err := os.Stat(conjBase); os.IsNotExist(err) {
err = extractZipToSiblingFolder(conjPath)
check(err)
}
// Now read metadata from the extracted directory
entries, err := os.ReadDir(conjBase)
check(err)
fmt.Println("Contents of", conjPath)
for _, f := range entries {
if f.Name() == "metadata.txt" {
rc, err := os.Open(filepath.Join(conjBase, f.Name()))
check(err)
defer rc.Close()
fmt.Println("Contents of metadata.txt:")
metadata, err := io.ReadAll(rc)
check(err)
game := parseGameInfo([]byte(escapeBackslashes(string(metadata))))
fmt.Println(game.ThumbnailPath)
games = append(games, game)
}
}
}
}
if len(games) > 0 {
fmt.Println("Found Conjure Games:", len(games))
} else {
fmt.Println("No Conjure games Found")
}
return games
}
// Function to escape backslashes in the YAML string
func escapeBackslashes(input string) string {
// Replace every single backslash with double backslashes
return strings.ReplaceAll(input, `\`, `\\`)
}
// Helper to print tree-like structure
func printIndentedPath(path string) {
parts := strings.Split(path, "/")
for i, part := range parts {
if part == "" {
continue
}
fmt.Print(strings.Repeat(" ", i))
fmt.Println(part)
}
}
func parseGameInfo(data []byte) models.Metadata {
game := models.Metadata{}
err := yaml.Unmarshal(data, &game)
check(err)
return game
}
func check(e error) {
if e != nil {
panic(e)
}
}