247 lines
6.5 KiB
Go
247 lines
6.5 KiB
Go
// 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
|
|
}
|