/ pkg / sources / disk.go
disk.go
  1  package source
  2  
  3  import (
  4  	"encoding/json"
  5  	"fmt"
  6  	"io"
  7  	"log"
  8  	"os"
  9  	"path/filepath"
 10  	"time"
 11  
 12  	dogeboxd "github.com/dogeorg/dogeboxd/pkg"
 13  	"github.com/dogeorg/dogeboxd/pkg/utils"
 14  )
 15  
 16  var _ dogeboxd.ManifestSource = &ManifestSourceDisk{}
 17  
 18  type ManifestSourceDisk struct {
 19  	config dogeboxd.ManifestSourceConfiguration
 20  }
 21  
 22  func (r ManifestSourceDisk) ValidateFromLocation(location string) (dogeboxd.ManifestSourceConfiguration, error) {
 23  	dogeboxPath := filepath.Join(location, "dogebox.json")
 24  	if _, err := os.Stat(dogeboxPath); err == nil {
 25  		content, err := os.ReadFile(dogeboxPath)
 26  		if err != nil {
 27  			return dogeboxd.ManifestSourceConfiguration{}, fmt.Errorf("failed to read dogebox.json: %w", err)
 28  		}
 29  
 30  		details, err := ParseAndValidateSourceDetails(string(content))
 31  		if err != nil {
 32  			return dogeboxd.ManifestSourceConfiguration{}, fmt.Errorf("failed to parse and validate dogebox.json: %w", err)
 33  		}
 34  
 35  		return dogeboxd.ManifestSourceConfiguration{
 36  			ID:          details.ID,
 37  			Name:        details.Name,
 38  			Description: details.Description,
 39  			Location:    location,
 40  			Type:        "disk",
 41  		}, nil
 42  	} else if os.IsNotExist(err) {
 43  		folder := filepath.Base(location)
 44  
 45  		return dogeboxd.ManifestSourceConfiguration{
 46  			ID:          folder,
 47  			Name:        folder,
 48  			Description: "",
 49  			Location:    location,
 50  			Type:        "disk",
 51  		}, nil
 52  	} else {
 53  		return dogeboxd.ManifestSourceConfiguration{}, fmt.Errorf("error accessing dogebox.json: %w", err)
 54  	}
 55  }
 56  
 57  func (r ManifestSourceDisk) Config() dogeboxd.ManifestSourceConfiguration {
 58  	return r.config
 59  }
 60  
 61  func (r ManifestSourceDisk) List(_ bool) (dogeboxd.ManifestSourceList, error) {
 62  	dogeboxPath := filepath.Join(r.config.Location, "dogebox.json")
 63  
 64  	pupLocations := []string{}
 65  
 66  	if _, err := os.Stat(dogeboxPath); err == nil {
 67  		content, err := os.ReadFile(dogeboxPath)
 68  		if err != nil {
 69  			return dogeboxd.ManifestSourceList{}, fmt.Errorf("failed to read dogebox.json: %w", err)
 70  		}
 71  
 72  		sourceIndex, err := ParseAndValidateSourceDetails(string(content))
 73  		if err != nil {
 74  			return dogeboxd.ManifestSourceList{}, fmt.Errorf("failed to parse and validate dogebox.json: %w", err)
 75  		}
 76  
 77  		for _, pupDetail := range sourceIndex.Pups {
 78  			pupLocations = append(pupLocations, filepath.Join(r.config.Location, pupDetail.Location))
 79  		}
 80  	} else {
 81  		pupLocations = append(pupLocations, r.config.Location)
 82  	}
 83  
 84  	pups := []dogeboxd.ManifestSourcePup{}
 85  
 86  outer:
 87  	for _, pupLocation := range pupLocations {
 88  		for _, filename := range REQUIRED_FILES {
 89  			p := filepath.Join(pupLocation, filename)
 90  			if _, err := os.Stat(p); os.IsNotExist(err) {
 91  				continue outer
 92  			}
 93  		}
 94  
 95  		manifestPath := filepath.Join(pupLocation, "manifest.json")
 96  		manifestData, err := os.ReadFile(manifestPath)
 97  		if err != nil {
 98  			return dogeboxd.ManifestSourceList{}, fmt.Errorf("failed to read manifest file: %w", err)
 99  		}
100  
101  		var manifest dogeboxd.PupManifest
102  		err = json.Unmarshal(manifestData, &manifest)
103  		if err != nil {
104  			return dogeboxd.ManifestSourceList{}, fmt.Errorf("failed to parse manifest file: %w", err)
105  		}
106  
107  		if err := manifest.Validate(); err != nil {
108  			return dogeboxd.ManifestSourceList{}, fmt.Errorf("manifest validation failed: %w", err)
109  		}
110  
111  		logoBase64 := ""
112  
113  		if manifest.Meta.LogoPath != "" {
114  			logoPath := filepath.Join(pupLocation, manifest.Meta.LogoPath)
115  			if _, err := os.Stat(logoPath); err == nil {
116  				logoData, err := os.ReadFile(logoPath)
117  				if err == nil {
118  					logoBase64, err = utils.ImageBytesToWebBase64(logoData, manifest.Meta.LogoPath)
119  					if err != nil {
120  						// Don't fail if we can't read/convert the logo for whatever reason.
121  						log.Printf("failed to read/convert logo for %s: %s", manifest.Meta.Name, err)
122  					}
123  				}
124  			}
125  		}
126  
127  		pup := dogeboxd.ManifestSourcePup{
128  			Name: manifest.Meta.Name,
129  			Location: map[string]string{
130  				"path": pupLocation,
131  			},
132  			Version:    manifest.Meta.Version,
133  			Manifest:   manifest,
134  			LogoBase64: logoBase64,
135  		}
136  
137  		pups = append(pups, pup)
138  	}
139  
140  	return dogeboxd.ManifestSourceList{
141  		Config:      r.config,
142  		LastChecked: time.Now(),
143  		Pups:        pups,
144  	}, nil
145  }
146  
147  func (r ManifestSourceDisk) Download(diskPath string, remoteLocation map[string]string) error {
148  	sourcePath := remoteLocation["path"]
149  
150  	// Copy the subpath to the final destination
151  	err := filepath.Walk(sourcePath, func(path string, info os.FileInfo, err error) error {
152  		if err != nil {
153  			return err
154  		}
155  
156  		relPath, err := filepath.Rel(sourcePath, path)
157  		if err != nil {
158  			return fmt.Errorf("failed to get relative path: %w", err)
159  		}
160  
161  		destPath := filepath.Join(diskPath, relPath)
162  
163  		if info.IsDir() {
164  			return os.MkdirAll(destPath, info.Mode())
165  		}
166  
167  		srcFile, err := os.Open(path)
168  		if err != nil {
169  			return fmt.Errorf("failed to open source file: %w", err)
170  		}
171  		defer srcFile.Close()
172  
173  		destFile, err := os.Create(destPath)
174  		if err != nil {
175  			return fmt.Errorf("failed to create destination file: %w", err)
176  		}
177  		defer destFile.Close()
178  
179  		_, err = io.Copy(destFile, srcFile)
180  		if err != nil {
181  			return fmt.Errorf("failed to copy file contents: %w", err)
182  		}
183  
184  		return os.Chmod(destPath, info.Mode())
185  	})
186  	if err != nil {
187  		return fmt.Errorf("failed to copy subpath to destination: %w", err)
188  	}
189  
190  	return nil
191  }