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 }