github.com/abemedia/appcast@v0.4.0/integrations/appinstaller/build.go (about) 1 package appinstaller 2 3 import ( 4 "archive/zip" 5 "bytes" 6 "context" 7 "encoding/xml" 8 "errors" 9 "path" 10 "strings" 11 12 "github.com/abemedia/appcast/source" 13 "github.com/abemedia/appcast/target" 14 ) 15 16 type Config struct { 17 OnLaunch *OnLaunchConfig 18 AutomaticBackgroundTask bool 19 ForceUpdateFromAnyVersion bool 20 21 Source *source.Source 22 Target target.Target 23 Version string 24 Prerelease bool 25 UploadPackages bool 26 } 27 28 type OnLaunchConfig struct { 29 HoursBetweenUpdateChecks int 30 ShowPrompt bool 31 UpdateBlocksActivation bool 32 } 33 34 var ErrNotValid = errors.New("not a valid bundle") 35 36 func Build(ctx context.Context, c *Config) error { 37 releases, err := c.Source.ListReleases(ctx, &source.ListOptions{ 38 Version: c.Version, 39 Prerelease: c.Prerelease, 40 }) 41 if err != nil { 42 return err 43 } 44 45 var r *source.Release 46 for _, r = range releases { 47 var ok bool 48 for _, asset := range r.Assets { 49 switch path.Ext(asset.Name) { 50 case ".msix", ".appx", ".msixbundle", ".appxbundle": 51 ok = true 52 if err := build(ctx, c, r.Version, asset); err != nil { 53 return err 54 } 55 } 56 } 57 if ok { 58 break // Only build for latest release containing supported files. 59 } 60 } 61 62 return nil 63 } 64 65 func build(ctx context.Context, c *Config, version string, asset *source.Asset) error { 66 b, err := c.Source.DownloadAsset(ctx, version, asset.Name) 67 if err != nil { 68 return err 69 } 70 71 p, err := getPackage(b) 72 if err != nil { 73 return err 74 } 75 76 if c.UploadPackages { 77 p.URI, err = upload(ctx, c.Target, asset.Name, b) 78 if err != nil { 79 return err 80 } 81 } else { 82 p.URI = asset.URL 83 } 84 85 res := newXML(c) 86 res.Version = p.Version 87 88 var name string 89 if strings.HasSuffix(asset.Name, "bundle") { 90 res.MainBundle = p 91 name = p.Name + ".appinstaller" 92 } else { 93 res.MainPackage = p 94 name = p.Name + "-" + p.ProcessorArchitecture + ".appinstaller" 95 } 96 97 res.URI, err = c.Target.URL(ctx, name) 98 if err != nil { 99 return err 100 } 101 102 return write(ctx, c, res) 103 } 104 105 func getPackage(b []byte) (*Package, error) { 106 r, err := zip.NewReader(bytes.NewReader(b), int64(len(b))) 107 if err != nil { 108 return nil, err 109 } 110 for _, f := range r.File { 111 if f.Name == "AppxManifest.xml" || f.Name == "AppxBundleManifest.xml" { 112 rc, err := f.Open() 113 if err != nil { 114 return nil, err 115 } 116 defer rc.Close() 117 var manifest struct{ Identity *Package } 118 if err = xml.NewDecoder(rc).Decode(&manifest); err != nil { 119 return nil, err 120 } 121 return manifest.Identity, nil 122 } 123 } 124 return nil, ErrNotValid 125 } 126 127 func upload(ctx context.Context, t target.Target, path string, data []byte) (string, error) { 128 w, err := t.NewWriter(ctx, path) 129 if err != nil { 130 return "", err 131 } 132 if _, err = w.Write(data); err != nil { 133 return "", err 134 } 135 if err = w.Close(); err != nil { 136 return "", err 137 } 138 return t.URL(ctx, path) 139 } 140 141 func newXML(c *Config) *AppInstaller { 142 appInstaller := &AppInstaller{} 143 144 // Get minimum required namespace. 145 switch { 146 case c.ForceUpdateFromAnyVersion, 147 c.OnLaunch != nil && c.OnLaunch.ShowPrompt, 148 c.OnLaunch != nil && c.OnLaunch.UpdateBlocksActivation: 149 appInstaller.XMLName.Space = "http://schemas.microsoft.com/appx/appinstaller/2018" 150 case c.AutomaticBackgroundTask, 151 c.OnLaunch != nil && c.OnLaunch.HoursBetweenUpdateChecks >= 0: 152 appInstaller.XMLName.Space = "http://schemas.microsoft.com/appx/appinstaller/2017/2" 153 default: 154 appInstaller.XMLName.Space = "http://schemas.microsoft.com/appx/appinstaller/2017" 155 return appInstaller 156 } 157 158 if c.OnLaunch != nil || c.AutomaticBackgroundTask || c.ForceUpdateFromAnyVersion { 159 appInstaller.UpdateSettings = &UpdateSettings{} 160 if c.AutomaticBackgroundTask { 161 appInstaller.UpdateSettings.AutomaticBackgroundTask = Bool(c.AutomaticBackgroundTask) 162 } 163 if c.ForceUpdateFromAnyVersion { 164 appInstaller.UpdateSettings.ForceUpdateFromAnyVersion = c.ForceUpdateFromAnyVersion 165 } 166 if c.OnLaunch != nil { 167 appInstaller.UpdateSettings.OnLaunch = (*OnLaunch)(c.OnLaunch) 168 } 169 } 170 171 return appInstaller 172 } 173 174 //nolint:gochecknoglobals 175 var replacer = strings.NewReplacer("></Package>", " />", "></Bundle>", " />", 176 "></MainPackage>", " />", "></MainBundle>", " />", "></OnLaunch>", " />", 177 "></AutomaticBackgroundTask>", " />", "></ForceUpdateFromAnyVersion>", " />") 178 179 func write(ctx context.Context, c *Config, app *AppInstaller) error { 180 w, err := c.Target.NewWriter(ctx, path.Base(app.URI)) 181 if err != nil { 182 return err 183 } 184 defer w.Close() 185 if _, err = w.Write([]byte(xml.Header)); err != nil { 186 return err 187 } 188 b, err := xml.MarshalIndent(app, "", "\t") 189 if err != nil { 190 return err 191 } 192 if _, err = replacer.WriteString(w, string(b)); err != nil { 193 return err 194 } 195 return w.Close() 196 }