~whereswaldon/rosebud

9c30c960d08b9fdd7138dbb000f2932be5f79021 — Chris Waldon 1 year, 3 months ago 9af2520
cmd/rosebus,deps: create new main pkg

This commit starts over on the main package, focusing on usability
by regular people. This commit adds just the flow to open a ledger
file, but includes backing that file up to a cache directory as a
precaution before doing any manipulation on it.

Signed-off-by: Chris Waldon <christopher.waldon.dev@gmail.com>
3 files changed, 159 insertions(+), 0 deletions(-)

A cmd/rosebud/main.go
M go.mod
M go.sum
A cmd/rosebud/main.go => cmd/rosebud/main.go +150 -0
@@ 0,0 1,150 @@
package main

import (
	"fmt"
	"io"
	"log"
	"os"
	"path/filepath"
	"time"

	"gioui.org/app"
	"gioui.org/io/system"
	"gioui.org/layout"
	"gioui.org/op"
	"gioui.org/x/explorer"
	"git.sr.ht/~gioverse/skel/bus"
	"git.sr.ht/~gioverse/skel/future"
	"git.sr.ht/~gioverse/skel/window"
	"github.com/howeyc/ledger"
)

func main() {
	go func() {
		bus := bus.New()
		windower := window.NewWindower(bus)
		window.NewWindowForBus(bus, loop, app.Title("Rosebud - tend to your finances"))
		windower.Run()
		os.Exit(0)
	}()
	app.Main()
}

type LoadStage byte

const (
	RequestingFiles LoadStage = iota
	LoadingFiles
	Ready
)

func loop(w *app.Window, conn bus.Connection) error {
	var ops op.Ops
	ui := UI{
		Explorer: explorer.NewExplorer(w),
		Conn:     conn,
	}
	for {
		select {
		case e := <-conn.Output():
			if future.TryResults(e) {
				w.Invalidate()
			}
		case e := <-w.Events():
			ui.Explorer.ListenEvents(e)
			switch e := e.(type) {
			case system.DestroyEvent:
				if e.Err != nil {
					log.Println("error: %v", e.Err)
				}
				return e.Err
			case system.FrameEvent:
				gtx := layout.NewContext(&ops, e)
				ui.Layout(gtx)
				e.Frame(gtx.Ops)
			}
		}
	}
}

type UI struct {
	Stage    LoadStage
	Explorer *explorer.Explorer
	Conn     bus.Connection
}

type (
	C = layout.Context
	D = layout.Dimensions
)

type Namer interface {
	Name() string
}

func (ui *UI) Layout(gtx C) D {
	type LoadInfo struct {
		Txs           []*ledger.Transaction
		OutFilePath   string
		CacheFilePath string
	}
	switch ui.Stage {
	case RequestingFiles:
		future.Run(ui.Conn,
			func() (li LoadInfo, err error) {
				cacheDir, err := os.UserCacheDir()
				if err != nil {
					return LoadInfo{}, fmt.Errorf("unable to resolve cache dir: %w", err)
				}
				confDir, err := os.UserConfigDir()
				if err != nil {
					return LoadInfo{}, fmt.Errorf("unable to resolve config dir: %w", err)
				}
				cacheDir = filepath.Join(cacheDir, "rosebud")
				if err := os.MkdirAll(cacheDir, 0o755); err != nil {
					return LoadInfo{}, fmt.Errorf("unable to make application cache dir: %w", err)
				}
				file, err := ui.Explorer.ChooseFile("txt", "ledger")
				if err != nil {
					return LoadInfo{}, err
				}
				outFilePath := filepath.Join(confDir, "rosebud", "budget.ledger")
				cacheFileName := "file"
				if namer, ok := file.(Namer); ok {
					name := namer.Name()
					outFilePath = name
					cacheFileName = filepath.Base(name)
				}

				defer file.Close()
				cacheFileName = fmt.Sprintf("%s-%s.gz", time.Now().Local().Format(time.RFC3339), cacheFileName)
				cacheFilePath := filepath.Join(cacheDir, cacheFileName)
				backupFile, err := os.Create(cacheFilePath)
				if err != nil {
					return LoadInfo{}, fmt.Errorf("unable to create backup file: %w", err)
				}
				defer func() {
					closeErr := backupFile.Close()
					if closeErr != nil && err == nil {
						err = fmt.Errorf("failed closing backup file: %w", closeErr)
					}
				}()
				tee := io.TeeReader(file, backupFile)
				transactions, err := ledger.ParseLedger(tee)
				if err != nil {
					return LoadInfo{}, fmt.Errorf("failed parsing input file: %w", err)
				}
				return LoadInfo{
					Txs:           transactions,
					OutFilePath:   outFilePath,
					CacheFilePath: cacheFilePath,
				}, nil
			},
			func(li LoadInfo, err error) bool {
				log.Printf("%v, %v", li, err)
				return true
			})
		ui.Stage = LoadingFiles
	}
	return D{}
}

M go.mod => go.mod +3 -0
@@ 5,15 5,18 @@ go 1.19
require (
	gioui.org v0.0.0-20221122135904-dee53b364560
	gioui.org/x v0.0.0-20221121204253-9da08d942944
	git.sr.ht/~gioverse/skel v0.0.0-20220916150537-2f38f089e413
	github.com/howeyc/ledger v0.3.5
)

require (
	gioui.org/cpu v0.0.0-20210817075930-8d6a761490d2 // indirect
	gioui.org/shader v1.0.6 // indirect
	git.wow.st/gmp/jni v0.0.0-20210610011705-34026c7e22d0 // indirect
	github.com/benoitkugler/textlayout v0.1.3 // indirect
	github.com/gioui/uax v0.2.1-0.20220819135011-cda973fac06d // indirect
	github.com/go-text/typesetting v0.0.0-20220411150340-35994bc27a7b // indirect
	github.com/godbus/dbus/v5 v5.0.6 // indirect
	github.com/joyt/godate v0.0.0-20150226210126-7151572574a7 // indirect
	github.com/marcmak/calc v0.0.0-20150509200512-5bbbfc3b3149 // indirect
	golang.org/x/exp/shiny v0.0.0-20220827204233-334a2380cb91 // indirect

M go.sum => go.sum +6 -0
@@ 8,6 8,10 @@ gioui.org/shader v1.0.6 h1:cvZmU+eODFR2545X+/8XucgZdTtEjR3QWW6W65b0q5Y=
gioui.org/shader v1.0.6/go.mod h1:mWdiME581d/kV7/iEhLmUgUK5iZ09XR5XpduXzbePVM=
gioui.org/x v0.0.0-20221121204253-9da08d942944 h1:1qB+TI3FwPFWzyqF6LzW4jhC3Vc+LbBnMqV7EvmJflI=
gioui.org/x v0.0.0-20221121204253-9da08d942944/go.mod h1:uhVlN625ysZfFEbEdxt+wgyCGW2sh5p6SYs9dr+w+u4=
git.sr.ht/~gioverse/skel v0.0.0-20220916150537-2f38f089e413 h1:rqEp9NqS0icb0xxSWpUe11sHf0bhwobxd+O6Wei6wGc=
git.sr.ht/~gioverse/skel v0.0.0-20220916150537-2f38f089e413/go.mod h1:DtJGzJPf/ONJskXDaxORcVjIFpKWN7mBq0R8j7c4BQM=
git.wow.st/gmp/jni v0.0.0-20210610011705-34026c7e22d0 h1:bGG/g4ypjrCJoSvFrP5hafr9PPB5aw8SjcOWWila7ZI=
git.wow.st/gmp/jni v0.0.0-20210610011705-34026c7e22d0/go.mod h1:+axXBRUTIDlCeE73IKeD/os7LoEnTKdkp8/gQOFjqyo=
github.com/benoitkugler/pstokenizer v1.0.0/go.mod h1:l1G2Voirz0q/jj0TQfabNxVsa8HZXh/VMxFSRALWTiE=
github.com/benoitkugler/textlayout v0.0.5/go.mod h1:puH4v13Uz7uIhIH0XMk5jgc8U3MXcn5r3VlV9K8n0D8=
github.com/benoitkugler/textlayout v0.1.3 h1:Jv0E28xDkke3KrWle90yOLtBmZsUqXLBy70lZRfbKN0=


@@ 17,6 21,8 @@ github.com/gioui/uax v0.2.1-0.20220819135011-cda973fac06d h1:ro1W5kY1pVBLHy4GokZ
github.com/gioui/uax v0.2.1-0.20220819135011-cda973fac06d/go.mod h1:b6uGh9ySJPVQG/RdiI88bE5sUGDk6vzzRujv1BAeuJc=
github.com/go-text/typesetting v0.0.0-20220411150340-35994bc27a7b h1:WINlj3ANt+CVrO2B4NGDHRlPvEWZPxjhb7z+JKypwXI=
github.com/go-text/typesetting v0.0.0-20220411150340-35994bc27a7b/go.mod h1:ZNYu5saGoMOqtkVH5T8onTwhzenDUVszI+5WFHJRaxQ=
github.com/godbus/dbus/v5 v5.0.6 h1:mkgN1ofwASrYnJ5W6U/BxG15eXXXjirgZc7CLqkcaro=
github.com/godbus/dbus/v5 v5.0.6/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/howeyc/ledger v0.3.5 h1:9EfDs73TzUUeK9W5spgAR1ELW5Ywd9lbd6ha25sgDf0=
github.com/howeyc/ledger v0.3.5/go.mod h1:Bt65DBbqSGABPEugJXzcbH1KOi3QBjIk/v6/AtoTkcM=
github.com/joyt/godate v0.0.0-20150226210126-7151572574a7 h1:2wH5antjhmU3EuWyidm0lJ4B9hGMpl5lNRo+M9uGJ5A=