package main import ( "crypto/sha1" "encoding/hex" "encoding/json" "errors" "fmt" "io/ioutil" "net/http" "os" "regexp" "sort" ) type Storage struct { Path string } var ( tokenPattern = regexp.MustCompile(`[a-fA-F0-9]{10,}`) ) func (s *Storage) MustInit() error { os.Mkdir(s.Path, 0755) os.Mkdir(s.Path+"/battles", 0755) os.Mkdir(s.Path+"/tokens", 0700) return nil } func (s *Storage) CanPost(token string) (bool, error) { if !tokenPattern.MatchString(token) { return false, errors.New("invalid token") } _, err := os.Stat(s.Path + "/tokens/" + token) return err == nil, nil } func (b *BattleStub) CalculateHash() string { hash := sha1.New() killmails := b.Killmails sort.Slice(killmails, func(lhs, rhs int) bool { return killmails[lhs].Id < killmails[rhs].Id }) for _, km := range killmails { fmt.Fprint(hash, km.Id) } sum := hash.Sum(nil) return hex.EncodeToString(sum[:]) } func (b *Battle) From(stub *BattleStub) error { if len(stub.Killmails) < 1 { return errors.New("missing killmails") } b.Id = stub.CalculateHash() b.Ships = make(map[int32]Ship) b.Locations = make(map[int32]Location) b.Names = make(map[int32]string) mark := func (id int32) { if id != 0 { b.Names[id] = "" } } for _, km := range stub.Killmails { details, err := GetKillmail(km.Id, km.Hash) if err != nil { return errors.New("could not retrieve killmail details") } details.Hash = km.Hash b.Locations[details.SolarSystemId] = Location{} b.Ships[details.Victim.ShipTypeId] = Ship{} mark(details.Victim.CharacterId) mark(details.Victim.CorporationId) mark(details.Victim.AllianceId) mark(details.Victim.FactionId) if b.StartTime.IsZero() || b.StartTime.After(details.Time) { b.StartTime = details.Time } if b.EndTime.IsZero() || b.EndTime.Before(details.Time) { b.EndTime = details.Time } b.Killmails = append(b.Killmails, details) } b.Teams = stub.Teams for key := range b.Ships { t, err := GetType(key) if err != nil { return errors.New("could not retrieve type details") } g, err := GetGroup(t.GroupId) if err != nil { return errors.New("could not retrieve group details") } b.Ships[key] = Ship{Name: t.Name, Group: g.Name} } for key := range b.Locations { s, err := GetSolarSystem(key) if err != nil { return errors.New("could not retrieve solar system details") } c, err := GetConstellation(s.ConstellationId) if err != nil { return errors.New("could not retrieve constellation details") } r, err := GetRegion(c.RegionId) if err != nil { return errors.New("could not retrieve region details") } b.Locations[key] = Location{ Name: s.Name, Security: s.Security, Position: s.Position, Constellation: c.Name, Region: r.Name, } if b.Name == "" { b.Name = fmt.Sprintf("Fight in %s", s.Name) } else { b.Name = fmt.Sprintf("%s, %s", b.Name, s.Name) } } // TODO: Resolve Names return nil } func (s *Storage) LoadBattle(battle *Battle) error { stream, err := os.Open(s.Path + "/battles/" + battle.Id) if err != nil { return err } decoder := json.NewDecoder(stream) if err = decoder.Decode(battle); err != nil { return err } return nil } func (s *Storage) AddBattle(battle *Battle) error { data, err := json.Marshal(battle) if err != nil { return err } return ioutil.WriteFile(s.Path+"/battles/"+battle.Id, data, 0644) } func (s *Storage) ServeBattle(id string, w http.ResponseWriter, r *http.Request) { w.Header().Add("Content-Type", "application/json") http.ServeFile(w, r, s.Path+"/battles/"+id) } func (s *Storage) ListRecentBattles(count int) ([]Battle, error) { files, err := ioutil.ReadDir(s.Path + "/battles") if err != nil { return nil, err } sort.Slice(files, func(lhs, rhs int) bool { return files[lhs].ModTime().After(files[rhs].ModTime()) }) battles := make([]Battle, 0, count) for i := 0; i < count && i < len(files); i++ { battle := Battle{Id: files[i].Name(), LastModified: files[i].ModTime()} if err = s.LoadBattle(&battle); err != nil { return nil, err } battles = append(battles, battle) } return battles, nil }