mirror of https://github.com/mvdan/fdroidcl.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
188 lines
3.9 KiB
188 lines
3.9 KiB
// Copyright (c) 2015, Daniel Martí <mvdan@mvdan.cc> |
|
// See LICENSE for licensing information |
|
|
|
package main |
|
|
|
import ( |
|
"bytes" |
|
"crypto/sha256" |
|
"encoding/gob" |
|
"fmt" |
|
"io" |
|
"io/ioutil" |
|
"log" |
|
"net/http" |
|
"os" |
|
"path/filepath" |
|
"sort" |
|
|
|
"github.com/mvdan/fdroidcl" |
|
) |
|
|
|
var cmdUpdate = &Command{ |
|
UsageLine: "update", |
|
Short: "Update the index", |
|
} |
|
|
|
func init() { |
|
cmdUpdate.Run = runUpdate |
|
} |
|
|
|
func runUpdate(args []string) { |
|
anyModified := false |
|
for _, r := range config.Repos { |
|
if !r.Enabled { |
|
continue |
|
} |
|
if err := r.updateIndex(); err == errNotModified { |
|
} else if err != nil { |
|
log.Fatalf("Could not update index: %v", err) |
|
} else { |
|
anyModified = true |
|
} |
|
} |
|
if anyModified { |
|
cache := filepath.Join(mustData(), "cache-gob") |
|
os.Remove(cache) |
|
} |
|
} |
|
|
|
const jarFile = "index.jar" |
|
|
|
func (r *repo) updateIndex() error { |
|
url := fmt.Sprintf("%s/%s", r.URL, jarFile) |
|
return downloadEtag(url, indexPath(r.ID), nil) |
|
} |
|
|
|
func (r *repo) loadIndex() (*fdroidcl.Index, error) { |
|
p := indexPath(r.ID) |
|
f, err := os.Open(p) |
|
if err != nil { |
|
return nil, fmt.Errorf("could not open index: %v", err) |
|
} |
|
stat, err := f.Stat() |
|
if err != nil { |
|
return nil, fmt.Errorf("could not stat index: %v", err) |
|
} |
|
//pubkey, err := hex.DecodeString(repoPubkey) |
|
//if err != nil { |
|
// return nil, fmt.Errorf("could not decode public key: %v", err) |
|
//} |
|
return fdroidcl.LoadIndexJar(f, stat.Size(), nil) |
|
} |
|
|
|
func respEtag(resp *http.Response) string { |
|
etags, e := resp.Header["Etag"] |
|
if !e || len(etags) == 0 { |
|
return "" |
|
} |
|
return etags[0] |
|
} |
|
|
|
var errNotModified = fmt.Errorf("not modified") |
|
|
|
func downloadEtag(url, path string, sum []byte) error { |
|
fmt.Printf("Downloading %s... ", url) |
|
defer fmt.Println() |
|
req, err := http.NewRequest("GET", url, nil) |
|
if err != nil { |
|
return err |
|
} |
|
|
|
etagPath := path + "-etag" |
|
if _, err := os.Stat(path); err == nil { |
|
etag, _ := ioutil.ReadFile(etagPath) |
|
req.Header.Add("If-None-Match", string(etag)) |
|
} |
|
|
|
client := &http.Client{} |
|
resp, err := client.Do(req) |
|
if err != nil { |
|
return err |
|
} |
|
defer resp.Body.Close() |
|
if resp.StatusCode >= 400 { |
|
return fmt.Errorf("download failed: %d %s", |
|
resp.StatusCode, http.StatusText(resp.StatusCode)) |
|
} |
|
if resp.StatusCode == http.StatusNotModified { |
|
fmt.Printf("not modified") |
|
return errNotModified |
|
} |
|
f, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0644) |
|
if err != nil { |
|
return err |
|
} |
|
defer f.Close() |
|
if sum == nil { |
|
_, err := io.Copy(f, resp.Body) |
|
if err != nil { |
|
return err |
|
} |
|
} else { |
|
data, err := ioutil.ReadAll(resp.Body) |
|
if err != nil { |
|
return err |
|
} |
|
got := sha256.Sum256(data) |
|
if !bytes.Equal(sum, got[:]) { |
|
return fmt.Errorf("sha256 mismatch") |
|
} |
|
if _, err := f.Write(data); err != nil { |
|
return err |
|
} |
|
} |
|
if err := ioutil.WriteFile(etagPath, []byte(respEtag(resp)), 0644); err != nil { |
|
return err |
|
} |
|
fmt.Printf("done") |
|
return nil |
|
} |
|
|
|
func indexPath(name string) string { |
|
return filepath.Join(mustData(), name+".jar") |
|
} |
|
|
|
func mustLoadIndexes() (apps []fdroidcl.App) { |
|
cache := filepath.Join(mustData(), "cache-gob") |
|
if f, err := os.Open(cache); err == nil { |
|
defer f.Close() |
|
if err := gob.NewDecoder(f).Decode(&apps); err == nil { |
|
return |
|
} |
|
} |
|
m := make(map[string]*fdroidcl.App) |
|
for _, r := range config.Repos { |
|
if !r.Enabled { |
|
continue |
|
} |
|
index, err := r.loadIndex() |
|
if err != nil { |
|
log.Fatalf("Error while loading %s: %v", r.ID, err) |
|
} |
|
for i := range index.Apps { |
|
app := index.Apps[i] |
|
orig, e := m[app.ID] |
|
if !e { |
|
m[app.ID] = &app |
|
continue |
|
} |
|
apks := append(orig.Apks, app.Apks...) |
|
// We use a stable sort so that repository order |
|
// (priority) is preserved amongst apks with the same |
|
// vercode on apps |
|
sort.Stable(fdroidcl.ApkList(apks)) |
|
m[app.ID].Apks = apks |
|
} |
|
} |
|
apps = make([]fdroidcl.App, 0, len(m)) |
|
for _, a := range m { |
|
apps = append(apps, *a) |
|
} |
|
sort.Sort(fdroidcl.AppList(apps)) |
|
if f, err := os.Create(cache); err == nil { |
|
defer f.Close() |
|
gob.NewEncoder(f).Encode(apps) |
|
} |
|
return |
|
}
|
|
|