| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207 |
- //go:build wasm
- // +build wasm
- package indexeddb
- import (
- "context"
- "sort"
- "sync"
- "sync/atomic"
- "github.com/hack-pad/go-indexeddb/idb"
- "github.com/hack-pad/hackpadfs"
- "github.com/hack-pad/hackpadfs/keyvalue"
- "github.com/hack-pad/hackpadfs/keyvalue/blob"
- )
- type transaction struct {
- ctx context.Context
- abort context.CancelFunc
- store *store
- txn *idb.Transaction
- results map[keyvalue.OpID]keyvalue.OpResult
- pendingResults []func()
- nextOp keyvalue.OpID
- resultsMu sync.Mutex
- }
- func (t *transaction) newOp() keyvalue.OpID {
- nextOp := atomic.AddInt64((*int64)(&t.nextOp), 1)
- return keyvalue.OpID(nextOp - 1)
- }
- func (t *transaction) setResult(op keyvalue.OpID, result keyvalue.OpResult) {
- t.resultsMu.Lock()
- t.results[op] = result
- t.resultsMu.Unlock()
- }
- func (t *transaction) setPendingResult(op keyvalue.OpID, req *getFileRequest) {
- t.resultsMu.Lock()
- t.pendingResults = append(t.pendingResults, func() {
- result := keyvalue.OpResult{Op: op}
- if req != nil {
- result.Record, result.Err = req.Result()
- }
- t.setResult(op, result)
- })
- t.resultsMu.Unlock()
- }
- func (t *transaction) setPendingValidateErr(op keyvalue.OpID, parentExistsReq *parentDirExistsReq) {
- if parentExistsReq == nil {
- return
- }
- t.resultsMu.Lock()
- t.pendingResults = append(t.pendingResults, func() {
- err := parentExistsReq.Err()
- t.setResult(op, keyvalue.OpResult{Op: op, Err: err})
- })
- t.resultsMu.Unlock()
- }
- func (t *transaction) Get(path string) (op keyvalue.OpID) {
- op = t.newOp()
- infos, err := t.txn.ObjectStore(infoStore)
- if err != nil {
- t.setResult(op, keyvalue.OpResult{Op: op, Err: err})
- return
- }
- req, err := t.store.getFile(infos, path)
- if err != nil {
- t.setResult(op, keyvalue.OpResult{Op: op, Err: err})
- return
- }
- t.setPendingResult(op, req)
- return
- }
- func (t *transaction) GetHandler(path string, handler keyvalue.OpHandler) (op keyvalue.OpID) {
- op = t.newOp()
- infos, err := t.txn.ObjectStore(infoStore)
- if err != nil {
- t.setResult(op, keyvalue.OpResult{Op: op, Err: err})
- return
- }
- req, err := t.store.getFile(infos, path)
- if err != nil {
- t.setResult(op, keyvalue.OpResult{Op: op, Err: err})
- return
- }
- listenErr := req.Listen(t.ctx, func() {
- record, err := req.Result()
- result := keyvalue.OpResult{
- Op: op,
- Record: record,
- Err: err,
- }
- if err := handler.Handle(t, result); err != nil {
- result.Err = err
- }
- t.setResult(op, result)
- }, func() {
- err := req.Err()
- t.setResult(op, keyvalue.OpResult{Op: op, Err: err})
- })
- if listenErr != nil {
- t.setResult(op, keyvalue.OpResult{Op: op, Err: listenErr})
- return
- }
- return
- }
- func (t *transaction) Set(name string, record keyvalue.FileRecord, contents blob.Blob) (op keyvalue.OpID) {
- op = t.newOp()
- _, err := t.set(op, name, record, contents)
- if err != nil {
- t.setResult(op, keyvalue.OpResult{Op: op, Err: err})
- return
- }
- return
- }
- func (t *transaction) set(op keyvalue.OpID, name string, record keyvalue.FileRecord, data blob.Blob) (*idb.Request, error) {
- infos, err := t.txn.ObjectStore(infoStore)
- if err != nil {
- return nil, err
- }
- contents, err := t.txn.ObjectStore(contentsStore)
- if err != nil {
- return nil, err
- }
- t.setResult(op, keyvalue.OpResult{Op: op}) // Ensure an op is recorded. A later result can overwrite it.
- if record == nil {
- if name == rootPath {
- return nil, hackpadfs.ErrNotImplemented // cannot delete root dir
- }
- req, err := deleteRecord(infos, contents, name)
- if err != nil {
- return nil, err
- }
- return req.Request, nil
- }
- if data != nil {
- // set file contents
- err := setFileContents(contents, name, data)
- if err != nil {
- return nil, err
- }
- }
- // always set metadata to update size when contents change
- req, parentExistsReq, err := validateAndSetFileMeta(t.ctx, infos, name, record, data)
- if err != nil {
- return nil, err
- }
- t.setPendingValidateErr(op, parentExistsReq)
- return req, nil
- }
- func (t *transaction) SetHandler(name string, record keyvalue.FileRecord, data blob.Blob, handler keyvalue.OpHandler) (op keyvalue.OpID) {
- op = t.newOp()
- req, err := t.set(op, name, record, data)
- if err != nil {
- t.setResult(op, keyvalue.OpResult{Op: op, Err: err})
- return
- }
- listenErr := req.Listen(t.ctx, func() {
- result := keyvalue.OpResult{Op: op, Err: req.Err()}
- if err := handler.Handle(t, result); err != nil {
- result.Err = err
- }
- t.setResult(op, result)
- }, func() {
- t.setResult(op, keyvalue.OpResult{Op: op, Err: req.Err()})
- })
- if listenErr != nil {
- t.setResult(op, keyvalue.OpResult{Op: op, Err: listenErr})
- return
- }
- return
- }
- func (t *transaction) Commit(ctx context.Context) ([]keyvalue.OpResult, error) {
- awaitErr := t.txn.Await(ctx)
- t.abort()
- for _, fn := range t.pendingResults {
- fn()
- }
- t.resultsMu.Lock()
- results := make([]keyvalue.OpResult, 0, len(t.results))
- for _, result := range t.results {
- results = append(results, result)
- }
- t.resultsMu.Unlock()
- sort.Slice(results, func(a, b int) bool {
- return results[a].Op < results[b].Op
- })
- return results, awaitErr
- }
- func (t *transaction) Abort() error {
- return t.txn.Abort()
- }
|