diff --git a/fs/group.go b/fs/group.go index 3d75a50..0485e5f 100644 --- a/fs/group.go +++ b/fs/group.go @@ -12,7 +12,9 @@ import ( type groupNode struct { fs.Inode param *FSParam - group *gitlab.Group + + group *gitlab.Group + staticNodes map[string]staticNode } // Ensure we are implementing the NodeReaddirer interface @@ -29,6 +31,9 @@ func newGroupNodeByID(gid int, param *FSParam) (*groupNode, error) { node := &groupNode{ param: param, group: group, + staticNodes: map[string]staticNode{ + ".refresh": newRefreshNode(group, param), + }, } return node, nil } @@ -37,13 +42,16 @@ func newGroupNode(group *gitlab.Group, param *FSParam) (*groupNode, error) { node := &groupNode{ param: param, group: group, + staticNodes: map[string]staticNode{ + ".refresh": newRefreshNode(group, param), + }, } return node, nil } func (n *groupNode) Readdir(ctx context.Context) (fs.DirStream, syscall.Errno) { groupContent, _ := n.param.Gitlab.FetchGroupContent(n.group) - entries := make([]fuse.DirEntry, 0, len(groupContent.Groups)+len(groupContent.Projects)) + entries := make([]fuse.DirEntry, 0, len(groupContent.Groups)+len(groupContent.Projects)+len(n.staticNodes)) for _, group := range groupContent.Groups { entries = append(entries, fuse.DirEntry{ Name: group.Name, @@ -58,6 +66,13 @@ func (n *groupNode) Readdir(ctx context.Context) (fs.DirStream, syscall.Errno) { Mode: fuse.S_IFLNK, }) } + for name, staticNode := range n.staticNodes { + entries = append(entries, fuse.DirEntry{ + Name: name, + Ino: staticNode.Ino(), + Mode: staticNode.Mode(), + }) + } return fs.NewListDirStream(entries), 0 } @@ -85,5 +100,16 @@ func (n *groupNode) Lookup(ctx context.Context, name string, out *fuse.EntryOut) repositoryNode, _ := newRepositoryNode(project, n.param) return n.NewInode(ctx, repositoryNode, attrs), 0 } + + // Check if the map of static nodes contains it + staticNode, ok := n.staticNodes[name] + if ok { + attrs := fs.StableAttr{ + Ino: staticNode.Ino(), + Mode: staticNode.Mode(), + } + return n.NewInode(ctx, staticNode, attrs), 0 + } + return nil, syscall.ENOENT } diff --git a/fs/refresh.go b/fs/refresh.go new file mode 100644 index 0000000..30f4974 --- /dev/null +++ b/fs/refresh.go @@ -0,0 +1,46 @@ +package fs + +import ( + "context" + "syscall" + + "github.com/badjware/gitlabfs/gitlab" + "github.com/hanwen/go-fuse/v2/fs" + "github.com/hanwen/go-fuse/v2/fuse" +) + +type refreshNode struct { + fs.Inode + ino uint64 + refresher gitlab.Refresher +} + +// Ensure we are implementing the NodeSetattrer interface +var _ = (fs.NodeSetattrer)((*refreshNode)(nil)) + +// Ensure we are implementing the NodeOpener interface +var _ = (fs.NodeOpener)((*refreshNode)(nil)) + +func newRefreshNode(refresher gitlab.Refresher, param *FSParam) *refreshNode { + return &refreshNode{ + ino: <-param.staticInoChan, + refresher: refresher, + } +} + +func (n *refreshNode) Ino() uint64 { + return n.ino +} + +func (n *refreshNode) Mode() uint32 { + return fuse.S_IFREG +} + +func (n *refreshNode) Setattr(ctx context.Context, fh fs.FileHandle, in *fuse.SetAttrIn, out *fuse.AttrOut) syscall.Errno { + return 0 +} + +func (n *refreshNode) Open(ctx context.Context, flags uint32) (fh fs.FileHandle, fuseFlags uint32, errno syscall.Errno) { + n.refresher.InvalidateCache() + return nil, 0, 0 +} diff --git a/fs/repository.go b/fs/repository.go index a614bf6..0bfe05e 100644 --- a/fs/repository.go +++ b/fs/repository.go @@ -18,7 +18,6 @@ type RepositoryNode struct { var _ = (fs.NodeReadlinker)((*RepositoryNode)(nil)) func newRepositoryNode(project *gitlab.Project, param *FSParam) (*RepositoryNode, error) { - node := &RepositoryNode{ param: param, project: project, diff --git a/fs/root.go b/fs/root.go index 95009f8..092ebdc 100644 --- a/fs/root.go +++ b/fs/root.go @@ -15,6 +15,12 @@ const ( staticInodeStart = uint64(int(^(uint(0))>>1)) + 1 ) +type staticNode interface { + fs.InodeEmbedder + Ino() uint64 + Mode() uint32 +} + type FSParam struct { Git git.GitClonerPuller Gitlab gitlab.GitlabFetcher diff --git a/fs/users.go b/fs/users.go index 0bdcabe..41aed95 100644 --- a/fs/users.go +++ b/fs/users.go @@ -71,7 +71,9 @@ func (n *usersNode) OnAdd(ctx context.Context) { type userNode struct { fs.Inode param *FSParam - user *gitlab.User + + user *gitlab.User + staticNodes map[string]staticNode } // Ensure we are implementing the NodeReaddirer interface @@ -88,6 +90,9 @@ func newUserNodeByID(uid int, param *FSParam) (*userNode, error) { node := &userNode{ param: param, user: user, + staticNodes: map[string]staticNode{ + ".refresh": newRefreshNode(user, param), + }, } return node, nil } @@ -96,13 +101,16 @@ func newUserNode(user *gitlab.User, param *FSParam) (*userNode, error) { node := &userNode{ param: param, user: user, + staticNodes: map[string]staticNode{ + ".refresh": newRefreshNode(user, param), + }, } return node, nil } func (n *userNode) Readdir(ctx context.Context) (fs.DirStream, syscall.Errno) { userContent, _ := n.param.Gitlab.FetchUserContent(n.user) - entries := make([]fuse.DirEntry, 0, len(userContent.Projects)) + entries := make([]fuse.DirEntry, 0, len(userContent.Projects)+len(n.staticNodes)) for _, project := range userContent.Projects { entries = append(entries, fuse.DirEntry{ Name: project.Name, @@ -110,11 +118,20 @@ func (n *userNode) Readdir(ctx context.Context) (fs.DirStream, syscall.Errno) { Mode: fuse.S_IFLNK, }) } + for name, staticNode := range n.staticNodes { + entries = append(entries, fuse.DirEntry{ + Name: name, + Ino: staticNode.Ino(), + Mode: staticNode.Mode(), + }) + } return fs.NewListDirStream(entries), 0 } func (n *userNode) Lookup(ctx context.Context, name string, out *fuse.EntryOut) (*fs.Inode, syscall.Errno) { userContent, _ := n.param.Gitlab.FetchUserContent(n.user) + + // Check if the map of projects contains it project, ok := userContent.Projects[name] if ok { attrs := fs.StableAttr{ @@ -124,5 +141,16 @@ func (n *userNode) Lookup(ctx context.Context, name string, out *fuse.EntryOut) repositoryNode, _ := newRepositoryNode(project, n.param) return n.NewInode(ctx, repositoryNode, attrs), 0 } + + // Check if the map of static nodes contains it + staticNode, ok := n.staticNodes[name] + if ok { + attrs := fs.StableAttr{ + Ino: staticNode.Ino(), + Mode: staticNode.Mode(), + } + return n.NewInode(ctx, staticNode, attrs), 0 + } + return nil, syscall.ENOENT } diff --git a/gitlab/client.go b/gitlab/client.go index 0776f61..e35b756 100644 --- a/gitlab/client.go +++ b/gitlab/client.go @@ -16,6 +16,10 @@ type GitlabFetcher interface { UserFetcher } +type Refresher interface { + InvalidateCache() +} + type GitlabClientParam struct { PullMethod string } diff --git a/gitlab/group.go b/gitlab/group.go index 25c913d..86a4c90 100644 --- a/gitlab/group.go +++ b/gitlab/group.go @@ -2,6 +2,7 @@ package gitlab import ( "fmt" + "sync" "github.com/xanzy/go-gitlab" ) @@ -20,6 +21,7 @@ type Group struct { ID int Name string + mux sync.Mutex content *GroupContent } @@ -31,6 +33,13 @@ func NewGroupFromGitlabGroup(group *gitlab.Group) Group { } } +func (g *Group) InvalidateCache() { + g.mux.Lock() + defer g.mux.Unlock() + + g.content = nil +} + func (c *gitlabClient) FetchGroup(gid int) (*Group, error) { gitlabGroup, _, err := c.client.Groups.GetGroup(gid) if err != nil { @@ -41,6 +50,10 @@ func (c *gitlabClient) FetchGroup(gid int) (*Group, error) { } func (c *gitlabClient) FetchGroupContent(group *Group) (*GroupContent, error) { + group.mux.Lock() + defer group.mux.Unlock() + + // Get cached data if available if group.content != nil { return group.content, nil } diff --git a/gitlab/user.go b/gitlab/user.go index 84f2a94..397341b 100644 --- a/gitlab/user.go +++ b/gitlab/user.go @@ -2,6 +2,7 @@ package gitlab import ( "fmt" + "sync" "github.com/xanzy/go-gitlab" ) @@ -20,6 +21,7 @@ type User struct { ID int Name string + mux sync.Mutex content *UserContent } @@ -31,6 +33,13 @@ func NewUserFromGitlabUser(user *gitlab.User) User { } } +func (u *User) InvalidateCache() { + u.mux.Lock() + defer u.mux.Unlock() + + u.content = nil +} + func (c *gitlabClient) FetchUser(uid int) (*User, error) { gitlabUser, _, err := c.client.Users.GetUser(uid) if err != nil { @@ -50,6 +59,10 @@ func (c *gitlabClient) FetchCurrentUser() (*User, error) { } func (c *gitlabClient) FetchUserContent(user *User) (*UserContent, error) { + user.mux.Lock() + defer user.mux.Unlock() + + // Get cached data if available if user.content != nil { return user.content, nil } diff --git a/go.sum b/go.sum index d0bb9cf..2f4bbd8 100644 --- a/go.sum +++ b/go.sum @@ -19,6 +19,7 @@ github.com/go-git/go-git v4.7.0+incompatible/go.mod h1:6+421e08gnZWn30y26Vchf7ef github.com/go-git/go-git-fixtures/v4 v4.0.2-0.20200613231340-f56387b50c12/go.mod h1:m+ICp2rF3jDhFgEZ/8yziagdT1C+ZpZcrJjappBCDSw= github.com/go-git/go-git/v5 v5.2.0 h1:YPBLG/3UK1we1ohRkncLjaXWLW+HKp5QNM/jTli2JgI= github.com/go-git/go-git/v5 v5.2.0/go.mod h1:kh02eMX+wdqqxgNMEyq8YgwlIOsDOa9homkUq1PoTMs= +github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk= @@ -96,6 +97,7 @@ golang.org/x/time v0.0.0-20191024005414-555d28b269f0 h1:/5xXl8Y5W96D+TtHSlonuFqG golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190729092621-ff9f1409240a/go.mod h1:jcCCGcm9btYwXyDqrUWc6MKQKKGJCWEQ3AfLSRIbEuI= +google.golang.org/appengine v1.3.0 h1:FBSsiFRMz3LBeXIomRnVzrQwSDj4ibvcRexLG0LZGQk= google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=