read/write functionality done
with price, shipping, purchase date, seller, and additional notes working for read and write. test included for exporting to/importing from string form pushed to discogs, which is validated before changes are made. worked on whole library.
This commit is contained in:
parent
a33a261a60
commit
9fe8a9b6fb
2
.gitignore
vendored
2
.gitignore
vendored
@ -1,3 +1,5 @@
|
||||
personal_access_token
|
||||
test.csv
|
||||
*.csv
|
||||
*.ods
|
||||
collection.gob
|
||||
|
263
main.go
263
main.go
@ -7,10 +7,12 @@ import (
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
"unicode"
|
||||
|
||||
"github.com/hrfee/go-discogs"
|
||||
)
|
||||
@ -25,7 +27,7 @@ var (
|
||||
|
||||
NotesFieldID = 4 // Test, otherwise use 3
|
||||
|
||||
noPaToken = errors.New(`put "hrfee:<personal access token>" in ./personal_access_token and re-run.`)
|
||||
noPaToken = errors.New(`put "<username>:<personal access token>" in ./personal_access_token and re-run.`)
|
||||
)
|
||||
|
||||
type ItemHash string
|
||||
@ -140,7 +142,7 @@ func (c *Client) CollectionJSON() (string, error) {
|
||||
return string(v), nil
|
||||
}
|
||||
|
||||
func (c *Client) WriteCSVTemplate(path string) error {
|
||||
func (c *Client) WriteCSVTemplate(path string, currency string) error {
|
||||
if c.CollectionCache.Order == nil || len(c.CollectionCache.Order) == 0 {
|
||||
if err := c.PopulateCollectionCache(); err != nil {
|
||||
return err
|
||||
@ -155,20 +157,123 @@ func (c *Client) WriteCSVTemplate(path string) error {
|
||||
}
|
||||
defer file.Close()
|
||||
w := csv.NewWriter(file)
|
||||
defer w.Flush()
|
||||
w.Write(HeaderRow)
|
||||
for _, ciHash := range c.CollectionCache.Order {
|
||||
w.Write(c.CollectionCache.Items[ciHash].RowFromCollectionItem())
|
||||
l := c.CollectionCache.Items[ciHash].RowFromCollectionItem(currency)
|
||||
err := w.Write(l)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("failed writing line \"%s\": %v", l, err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Client) ReadFromFilledCSV(path string, currency string, skipFirstNRows int) (int, error) {
|
||||
file, err := os.Open(path)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
defer file.Close()
|
||||
r := csv.NewReader(file)
|
||||
i := 0
|
||||
for err == nil {
|
||||
var l []string
|
||||
l, err = r.Read()
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
|
||||
if l[0] == "Folder ID" {
|
||||
continue
|
||||
}
|
||||
|
||||
if i < skipFirstNRows {
|
||||
i++
|
||||
fmt.Printf("skipped row %d = \"%s\"\n", i, l[cArtistName])
|
||||
continue
|
||||
}
|
||||
|
||||
pp := parsePrice(l[cPrice], currency)
|
||||
pps := parsePrice(l[cShipping], currency)
|
||||
|
||||
pn := ParsedNotes{
|
||||
Price: pp,
|
||||
ShippingPrice: pps,
|
||||
Seller: l[cSeller],
|
||||
Additional: l[cAdditionalNotes],
|
||||
}
|
||||
pn.PurchaseDate, err = time.Parse("2006-01-02", l[cPurchaseDate])
|
||||
if err != nil {
|
||||
return i, err
|
||||
}
|
||||
|
||||
/*folderID, _ := strconv.Atoi(l[cFolderID])
|
||||
releaseID, _ := strconv.Atoi(l[cReleaseID])
|
||||
instanceID, _ := strconv.Atoi(l[cInstanceID])*/
|
||||
|
||||
pnString := pn.ToString(currency)
|
||||
|
||||
// fmt.Printf("For \"%s\": %s\n", l[cArtistName], pnString)
|
||||
regenerated := parseNotes(pnString, "2006-01-02T15:04:05-07:00", currency)
|
||||
pn.RoughPanickingEqual(regenerated)
|
||||
|
||||
folderID, err := strconv.Atoi(l[cFolderID])
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
releaseID, err := strconv.Atoi(l[cReleaseID])
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
instanceID, err := strconv.Atoi(l[cInstanceID])
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
err = c.c.EditFieldsInstance(
|
||||
c.Username,
|
||||
folderID,
|
||||
releaseID,
|
||||
instanceID,
|
||||
discogs.NotesField,
|
||||
pnString,
|
||||
)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("failed on %d-%d-%d \"%s\": %v", folderID, releaseID, instanceID, l[cArtistName], err))
|
||||
}
|
||||
|
||||
fmt.Printf("logged change for %d = \"%s\"\n", i, l[cArtistName])
|
||||
i++
|
||||
}
|
||||
if errors.Is(err, io.EOF) {
|
||||
return i, nil
|
||||
}
|
||||
return i, err
|
||||
}
|
||||
|
||||
func (c *Client) TestEditField() {
|
||||
err := c.c.EditFieldsInstance(c.Username, 1, 10191384, 313879623, discogs.NotesField, "a new 2nd value!")
|
||||
fmt.Printf("Error: %v\n", err)
|
||||
}
|
||||
|
||||
const (
|
||||
cFolderID = iota
|
||||
cReleaseID
|
||||
cInstanceID
|
||||
cArtistName
|
||||
cFormat
|
||||
cDateAdded
|
||||
cPrice
|
||||
cShipping
|
||||
cPurchaseDate
|
||||
cSeller
|
||||
cAdditionalNotes
|
||||
)
|
||||
|
||||
var HeaderRow = strings.Split(
|
||||
"Folder ID,Release ID,Instance ID,Artist - Name,Date Added,Price,Purchased Date,Additional Notes",
|
||||
"Folder ID,Release ID,Instance ID,Artist - Name,Format,Date Added,Price,Shipping (p/ Item),Purchased Date,Seller,Additional Notes",
|
||||
",",
|
||||
)
|
||||
|
||||
@ -196,40 +301,156 @@ func (ci CollectionItem) RawNotes() string {
|
||||
}
|
||||
|
||||
type ParsedNotes struct {
|
||||
Price string
|
||||
PurchaseDate time.Time
|
||||
Additional string
|
||||
Price ParsedPrice
|
||||
ShippingPrice ParsedPrice
|
||||
PurchaseDate time.Time
|
||||
Seller string
|
||||
Additional string
|
||||
}
|
||||
|
||||
func (ci CollectionItem) ParseNotes() ParsedNotes {
|
||||
func (p1 ParsedNotes) RoughPanickingEqual(p2 ParsedNotes) {
|
||||
if p1.Price.String() != p2.Price.String() {
|
||||
panic(fmt.Errorf("p1.Price != p2.Price: %v != %v", p1.Price, p2.Price))
|
||||
}
|
||||
if p1.ShippingPrice.String() != p2.ShippingPrice.String() {
|
||||
panic(fmt.Errorf("p1.ShippingPrice != p2.ShippingPrice: %v != %v", p1.ShippingPrice, p2.ShippingPrice))
|
||||
}
|
||||
// Don't care about date
|
||||
if p1.Seller != p2.Seller {
|
||||
panic(fmt.Errorf("p1.Seller != p2.Seller: \"%v\" != \"%v\"", p1.Seller, p2.Seller))
|
||||
}
|
||||
if p1.Additional != p2.Additional {
|
||||
panic(fmt.Errorf("p1.Additional != p2.Additional: \"%v\" != \"%v\"", p1.Additional, p2.Additional))
|
||||
}
|
||||
}
|
||||
|
||||
type ParsedPrice struct {
|
||||
Currency string
|
||||
Major, Minor int
|
||||
}
|
||||
|
||||
func (pp ParsedPrice) String() string {
|
||||
return fmt.Sprintf("%s%d.%02d", pp.Currency, pp.Major, pp.Minor)
|
||||
}
|
||||
|
||||
func (pp *ParsedPrice) FromFloat(p float64) {
|
||||
pp.Major = int(p)
|
||||
pp.Minor = int((float64(p) - float64(int(p))) * float64(100))
|
||||
}
|
||||
|
||||
func (pp ParsedPrice) IsZero() bool { return pp.Major == 0 && pp.Minor == 0 }
|
||||
|
||||
func parsePrice(p string, currency string) ParsedPrice {
|
||||
parts := strings.SplitN(p, ".", 2)
|
||||
major := parts[0]
|
||||
existingCurrency := ""
|
||||
for i := 0; i < len(major); i++ {
|
||||
if !unicode.IsDigit(rune(major[i])) {
|
||||
existingCurrency += string(rune(major[i]))
|
||||
} else {
|
||||
major = major[i:]
|
||||
break
|
||||
}
|
||||
}
|
||||
iMajor, _ := strconv.Atoi(major)
|
||||
iMinor := 0
|
||||
if len(parts) > 1 {
|
||||
parts[1] = strings.ReplaceAll(parts[1], " ", "")
|
||||
var err error
|
||||
iMinor, err = strconv.Atoi(parts[1])
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
pp := ParsedPrice{
|
||||
Currency: existingCurrency,
|
||||
Major: iMajor,
|
||||
Minor: iMinor,
|
||||
}
|
||||
if currency != "" {
|
||||
pp.Currency = currency
|
||||
}
|
||||
return pp
|
||||
}
|
||||
|
||||
func (pn ParsedNotes) ToString(currency string) string {
|
||||
price := pn.Price.String()
|
||||
if !pn.ShippingPrice.IsZero() {
|
||||
price += " (+" + pn.ShippingPrice.String() + ")"
|
||||
}
|
||||
return fmt.Sprintf("Price: %s, Purchase Date: %s, From: %s. %s", price, pn.PurchaseDate.Format("2006-01-02"), pn.Seller, pn.Additional)
|
||||
}
|
||||
|
||||
func (ci CollectionItem) ParseNotes(currency string) ParsedNotes {
|
||||
notes := ci.RawNotes()
|
||||
return parseNotes(notes, ci.DateAdded, currency)
|
||||
}
|
||||
|
||||
func parseNotes(notes string, dateAdded string, currency string) ParsedNotes {
|
||||
pn := ParsedNotes{}
|
||||
dateString := ""
|
||||
n, err := fmt.Sscanf(notes, "Purchase Price: %s, Purchase Date: %s.%s", &(pn.Price), &dateString, &(pn.Additional))
|
||||
priceString := ""
|
||||
if strings.HasPrefix(notes, "Price:") {
|
||||
noPrice := strings.TrimPrefix(notes, "Price: ")
|
||||
priceString = strings.Split(notes, ", Purchase Date")[0]
|
||||
notes = "P" + strings.SplitN(noPrice, ", P", 2)[1]
|
||||
}
|
||||
// fmt.Printf("Parsing \"%s\"\n", notes)
|
||||
n, err := fmt.Sscanf(notes, "Purchase Date: %s, From: %s. %s", &dateString, &(pn.Seller), &(pn.Additional))
|
||||
if n <= 0 || err != nil {
|
||||
pn.Additional = notes
|
||||
}
|
||||
|
||||
end := strings.Split(notes, "From: ")
|
||||
if len(end) > 1 {
|
||||
splitInd := strings.Index(end[1], ". ")
|
||||
if splitInd != -1 {
|
||||
pn.Seller = end[1][:splitInd]
|
||||
pn.Additional = end[1][splitInd+len(". "):]
|
||||
}
|
||||
}
|
||||
|
||||
priceSplit := strings.Split(priceString, "(+")
|
||||
// fmt.Printf("ps \"%s\" split to %+v\n", priceString, priceSplit)
|
||||
// fmt.Printf("got dateString \"%s\"\n", dateString)
|
||||
pn.Price = parsePrice(priceSplit[0], currency)
|
||||
pn.ShippingPrice = ParsedPrice{Currency: currency}
|
||||
if len(priceSplit) > 1 {
|
||||
pn.ShippingPrice = parsePrice(strings.ReplaceAll(priceSplit[1], ")", ""), currency)
|
||||
}
|
||||
|
||||
dateString = strings.TrimSuffix(dateString, ",")
|
||||
pn.PurchaseDate, err = time.Parse("2006-01-02", dateString)
|
||||
if err != nil || dateString == "" || pn.PurchaseDate.IsZero() {
|
||||
pn.PurchaseDate, _ = ParseTime(ci.DateAdded)
|
||||
if dateString != "" {
|
||||
fmt.Printf("date error: %v\n", err)
|
||||
}
|
||||
pn.PurchaseDate, _ = ParseTime(dateAdded)
|
||||
}
|
||||
return pn
|
||||
}
|
||||
|
||||
func (ci CollectionItem) RowFromCollectionItem() []string {
|
||||
func (ci CollectionItem) RowFromCollectionItem(currency string) []string {
|
||||
dateAdded, err := ParseTime(ci.DateAdded)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
notes := ci.ParseNotes()
|
||||
format := "?"
|
||||
if len(ci.BasicInformation.Formats) != 0 {
|
||||
format = ci.BasicInformation.Formats[0].Name
|
||||
}
|
||||
notes := ci.ParseNotes(currency)
|
||||
row := []string{
|
||||
strconv.Itoa(ci.FolderID),
|
||||
strconv.Itoa(ci.ID),
|
||||
strconv.Itoa(ci.InstanceID),
|
||||
ci.BasicInformation.Artists[0].Name + " - " + ci.BasicInformation.Title,
|
||||
format,
|
||||
dateAdded.Format("2006-01-02"),
|
||||
notes.Price,
|
||||
notes.Price.String(),
|
||||
notes.ShippingPrice.String(),
|
||||
notes.PurchaseDate.Format("2006-01-02"),
|
||||
notes.Seller,
|
||||
notes.Additional,
|
||||
}
|
||||
return row
|
||||
@ -253,9 +474,16 @@ func main() {
|
||||
reloadCollection := false
|
||||
printCollection := false
|
||||
csvTemplatePath := ""
|
||||
csvApplyFromPath := ""
|
||||
skipRowsOnApplyFrom := 0
|
||||
currency := "£"
|
||||
|
||||
flag.BoolVar(&reloadCollection, "reload", false, "pass to force reload of collection cache.")
|
||||
flag.BoolVar(&printCollection, "print", false, "pass to print JSON representation of collection.")
|
||||
flag.StringVar(&csvTemplatePath, "template", "", "filepath to write template csv to.")
|
||||
flag.StringVar(&csvApplyFromPath, "apply", "", "filepath to csv to apply notes from.")
|
||||
flag.IntVar(&skipRowsOnApplyFrom, "skip", 0, "number of rows to skip applying from, useful if previous attempt failed.")
|
||||
flag.StringVar(¤cy, "currency", "£", "currency symbol to fill in.")
|
||||
|
||||
flag.Parse()
|
||||
|
||||
@ -281,10 +509,17 @@ func main() {
|
||||
}
|
||||
|
||||
if csvTemplatePath != "" {
|
||||
if err := c.WriteCSVTemplate(csvTemplatePath); err != nil {
|
||||
if err := c.WriteCSVTemplate(csvTemplatePath, currency); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
if csvApplyFromPath != "" {
|
||||
n, err := c.ReadFromFilledCSV(csvApplyFromPath, currency, skipRowsOnApplyFrom)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("failed at %d: %v", n, err))
|
||||
}
|
||||
}
|
||||
|
||||
// c.TestEditField()
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user