// Copyright © 2011-12 Qtrac Ltd. // // This program or package and any associated files are licensed under the // Apache License, Version 2.0 (the "License"); you may not use these files // except in compliance with the License. You can get a copy of the License // at: http://www.apache.org/licenses/LICENSE-2.0. // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package main import ( "encoding/binary" "errors" "fmt" "io" "strconv" "time" ) type InvMarshaler struct{} const invDateFormat = "20060102" // This date must always be used. var byteOrder = binary.LittleEndian func (InvMarshaler) MarshalInvoices(writer io.Writer, invoices []*Invoice) error { var write invWriterFunc = func(x interface{}) error { return binary.Write(writer, byteOrder, x) } if err := write(uint32(magicNumber)); err != nil { return err } if err := write(uint16(fileVersion)); err != nil { return err } if err := write(int32(len(invoices))); err != nil { return err } for _, invoice := range invoices { if err := write.writeInvoice(invoice); err != nil { return err } } return nil } type invWriterFunc func(interface{}) error func (write invWriterFunc) writeInvoice(invoice *Invoice) error { for _, i := range []int{invoice.Id, invoice.CustomerId} { if err := write(int32(i)); err != nil { return err } } for _, date := range []time.Time{invoice.Raised, invoice.Due} { if err := write.writeDate(date); err != nil { return err } } if err := write.writeBool(invoice.Paid); err != nil { return err } if err := write.writeString(invoice.Note); err != nil { return err } if err := write(int32(len(invoice.Items))); err != nil { return err } for _, item := range invoice.Items { if err := write.writeItem(item); err != nil { return err } } return nil } func (write invWriterFunc) writeDate(date time.Time) error { i, err := strconv.Atoi(date.Format(invDateFormat)) if err != nil { return err } return write(int32(i)) } func (write invWriterFunc) writeBool(b bool) error { var v int8 if b { v = 1 } return write(v) } func (write invWriterFunc) writeString(s string) error { if err := write(int32(len(s))); err != nil { return err } return write([]byte(s)) } func (write invWriterFunc) writeItem(item *Item) error { if err := write.writeString(item.Id); err != nil { return err } if err := write(item.Price); err != nil { return err } if err := write(int16(item.Quantity)); err != nil { return err } return write.writeString(item.Note) } func (InvMarshaler) UnmarshalInvoices(reader io.Reader) ([]*Invoice, error) { if err := checkInvVersion(reader); err != nil { return nil, err } count, err := readIntFromInt32(reader) if err != nil { return nil, err } invoices := make([]*Invoice, 0, count) for i := 0; i < count; i++ { invoice, err := readInvInvoice(reader) if err != nil { return nil, err } invoices = append(invoices, invoice) } return invoices, nil } func readIntFromInt32(reader io.Reader) (int, error) { var i32 int32 err := binary.Read(reader, byteOrder, &i32) return int(i32), err } func readIntFromInt16(reader io.Reader) (int, error) { var i16 int16 err := binary.Read(reader, byteOrder, &i16) return int(i16), err } func readBoolFromInt8(reader io.Reader) (bool, error) { var i8 int8 err := binary.Read(reader, byteOrder, &i8) return i8 == 1, err } func checkInvVersion(reader io.Reader) error { var magic uint32 if err := binary.Read(reader, byteOrder, &magic); err != nil { return err } if magic != magicNumber { return errors.New("cannot read non-invoices inv file") } var version uint16 if err := binary.Read(reader, byteOrder, &version); err != nil { return err } if version > fileVersion { return fmt.Errorf("version %d is too new to read", version) } return nil } func readInvInvoice(reader io.Reader) (invoice *Invoice, err error) { invoice = &Invoice{} for _, pId := range []*int{&invoice.Id, &invoice.CustomerId} { if *pId, err = readIntFromInt32(reader); err != nil { return nil, err } } for _, pDate := range []*time.Time{&invoice.Raised, &invoice.Due} { if *pDate, err = readInvDate(reader); err != nil { return nil, err } } if invoice.Paid, err = readBoolFromInt8(reader); err != nil { return nil, err } if invoice.Note, err = readInvString(reader); err != nil { return nil, err } var count int if count, err = readIntFromInt32(reader); err != nil { return nil, err } invoice.Items, err = readInvItems(reader, count) return invoice, err } func readInvItems(reader io.Reader, count int) ([]*Item, error) { items := make([]*Item, 0, count) for i := 0; i < count; i++ { item, err := readInvItem(reader) if err != nil { return nil, err } items = append(items, item) } return items, nil } func readInvDate(reader io.Reader) (time.Time, error) { var n int32 if err := binary.Read(reader, byteOrder, &n); err != nil { return time.Time{}, err } return time.Parse(invDateFormat, fmt.Sprint(n)) } func readInvString(reader io.Reader) (string, error) { var length int32 if err := binary.Read(reader, byteOrder, &length); err != nil { return "", nil } raw := make([]byte, length) if err := binary.Read(reader, byteOrder, &raw); err != nil { return "", err } return string(raw), nil } func readInvItem(reader io.Reader) (item *Item, err error) { item = &Item{} if item.Id, err = readInvString(reader); err != nil { return nil, err } if err = binary.Read(reader, byteOrder, &item.Price); err != nil { return nil, err } if item.Quantity, err = readIntFromInt16(reader); err != nil { return nil, err } item.Note, err = readInvString(reader) return item, nil }