// Copyright Earl Warren // Copyright Loïc Dachary // SPDX-License-Identifier: MIT package filesystem import ( "context" "fmt" "os" "path/filepath" "strings" "code.forgejo.org/f3/gof3/v3/f3" "code.forgejo.org/f3/gof3/v3/id" "code.forgejo.org/f3/gof3/v3/kind" f3_tree "code.forgejo.org/f3/gof3/v3/tree/f3" "code.forgejo.org/f3/gof3/v3/tree/generic" "code.forgejo.org/f3/gof3/v3/util" ) type nodeDriver struct { generic.NullDriver content f3.Interface } func newNodeDriver(content f3.Interface) generic.NodeDriverInterface { return &nodeDriver{ content: content.Clone(), } } func (o *nodeDriver) SetNative(any) {} func (o *nodeDriver) GetNativeID() string { return o.GetNode().GetID().String() } func (o *nodeDriver) getBasePath() string { options := o.GetTreeDriver().(*treeDriver).options return options.Directory + o.GetNode().GetCurrentPath().String() } func (o *nodeDriver) getTree() generic.TreeInterface { return o.GetNode().GetTree() } func (o *nodeDriver) getF3Tree() f3_tree.TreeInterface { return o.getTree().(f3_tree.TreeInterface) } func (o *nodeDriver) isContainer() bool { return o.getF3Tree().IsContainer(o.getKind()) } func (o *nodeDriver) getKind() kind.Kind { return o.GetNode().GetKind() } func (o *nodeDriver) IsNull() bool { return false } func (o *nodeDriver) GetIDFromName(ctx context.Context, name string) id.NodeID { switch o.getKind() { case kind.KindRoot, f3_tree.KindProjects, f3_tree.KindUsers, f3_tree.KindOrganizations, f3_tree.KindRepositories: default: panic(fmt.Errorf("unxpected kind %s", o.getKind())) } for _, child := range o.GetNode().List(ctx) { child.Get(ctx) if child.ToFormat().GetName() == name { return child.GetID() } } return id.NilID } func (o *nodeDriver) ListPage(ctx context.Context, page int) generic.ChildrenSlice { node := o.GetNode() node.Trace("%s '%s'", o.getKind(), node.GetID()) children := generic.NewChildrenSlice(0) if o.getKind() == kind.KindRoot || page > 1 { return children } basePath := o.getBasePath() if !util.FileExists(basePath) { return children } f3Tree := o.getF3Tree() if !f3Tree.IsContainer(o.getKind()) { return children } node.Trace("%d '%s'", page, basePath) dirEntries, err := os.ReadDir(basePath) if err != nil { panic(fmt.Errorf("ReadDir %s %w", basePath, err)) } for _, dirEntry := range dirEntries { if !strings.HasSuffix(dirEntry.Name(), ".json") { continue } node.Trace(" add %s", dirEntry.Name()) child := node.CreateChild(ctx) i := strings.TrimSuffix(dirEntry.Name(), ".json") childID := id.NewNodeID(i) child.SetID(childID) children = append(children, child) } return children } func (o *nodeDriver) Equals(context.Context, generic.NodeInterface) bool { panic("") } func (o *nodeDriver) LookupMappedID(id id.NodeID) id.NodeID { o.GetNode().Trace("%s", id) return id } func (o *nodeDriver) hasJSON() bool { kind := o.getKind() if kind == f3_tree.KindForge { return true } return !o.isContainer() } func (o *nodeDriver) Get(context.Context) bool { o.GetNode().Trace("'%s' '%s'", o.getKind(), o.GetNode().GetID()) if !o.hasJSON() || o.GetNode().GetID() == id.NilID { return true } filename := o.getBasePath() + ".json" o.GetNode().Trace("'%s'", filename) if !util.FileExists(filename) { return false } f := o.NewFormat() loadJSON(filename, f) o.content = f o.GetNode().Trace("%s %s id=%s", o.getKind(), filename, o.content.GetID()) idFilename := o.getBasePath() + ".id" if !util.FileExists(idFilename) { return true } mappedID, err := os.ReadFile(idFilename) if err != nil { panic(fmt.Errorf("Get %s %w", idFilename, err)) } o.NullDriver.SetMappedID(id.NewNodeID(string(mappedID))) return true } func (o *nodeDriver) SetMappedID(mapped id.NodeID) { o.NullDriver.SetMappedID(mapped) o.saveMappedID() } func (o *nodeDriver) saveMappedID() { k := o.getKind() switch k { case kind.KindRoot, f3_tree.KindForge: return } if o.isContainer() { return } mappedID := o.GetMappedID() if mappedID == id.NilID { return } basePath := o.getBasePath() idFilename := basePath + ".id" o.Trace("%s", idFilename) if err := os.WriteFile(idFilename, []byte(o.GetMappedID().String()), 0o644); err != nil { panic(fmt.Errorf("%s %w", idFilename, err)) } } func (o *nodeDriver) Put(ctx context.Context) id.NodeID { return o.upsert(ctx) } func (o *nodeDriver) Patch(ctx context.Context) { o.upsert(ctx) } func (o *nodeDriver) upsert(context.Context) id.NodeID { i := o.GetNode().GetID() o.GetNode().Trace("%s %s", o.getKind(), i) o.content.SetID(i.String()) if !o.hasJSON() || i == id.NilID { return i } basePath := o.getBasePath() dirname := filepath.Dir(basePath) if !util.FileExists(dirname) { if err := os.MkdirAll(dirname, 0o777); err != nil { panic(fmt.Errorf("MakeDirAll %s %w", dirname, err)) } } saveJSON(basePath+".json", o.content) o.saveMappedID() return i } func (o *nodeDriver) Delete(context.Context) { if o.isContainer() { return } basePath := o.getBasePath() if util.FileExists(basePath) { if err := os.RemoveAll(basePath); err != nil { panic(fmt.Errorf("RemoveAll %s %w", basePath, err)) } } for _, ext := range []string{".id", ".json"} { jsonFilename := basePath + ext if util.FileExists(jsonFilename) { if err := os.Remove(jsonFilename); err != nil { panic(fmt.Errorf("RemoveAll %s %w", basePath, err)) } } } o.content = o.NewFormat() } func (o *nodeDriver) NewFormat() f3.Interface { return o.getTree().(f3_tree.TreeInterface).NewFormat(o.getKind()) } func (o *nodeDriver) FromFormat(content f3.Interface) { o.content = content } func (o *nodeDriver) ToFormat() f3.Interface { return o.content.Clone() } func (o *nodeDriver) String() string { return o.content.GetID() }