file.go (4432B)
1 package hub 2 3 import ( 4 "bytes" 5 "fmt" 6 "io" 7 "io/fs" 8 "strconv" 9 "time" 10 ) 11 12 type ftype uint8 13 14 const ( 15 ftypeRoot = iota 16 ftypeIssueDir 17 ftypeIssue 18 ftypeCommentDir 19 ftypeComment 20 ) 21 22 type fakeFile struct { 23 *Client 24 owner, repo string 25 typ ftype 26 27 // comment, issue files 28 number int 29 buf *bytes.Buffer 30 31 // directory files 32 children []fs.DirEntry 33 dirp int 34 } 35 36 func (f *fakeFile) Name() string { 37 switch f.typ { 38 case ftypeRoot: 39 return "." 40 case ftypeIssueDir: 41 return strconv.Itoa(f.number) 42 case ftypeIssue: 43 return "issue" 44 case ftypeCommentDir: 45 return "comments" 46 case ftypeComment: 47 return strconv.Itoa(f.number) 48 } 49 return "unknown" 50 } 51 52 func (f *fakeFile) IsDir() bool { 53 switch f.typ { 54 case ftypeRoot, ftypeIssueDir, ftypeCommentDir: 55 return true 56 } 57 return false 58 } 59 60 func (f *fakeFile) Type() fs.FileMode { 61 if f.IsDir() { 62 return fs.ModeDir 63 } 64 return 0 65 } 66 67 func (f *fakeFile) Info() (fs.FileInfo, error) { return f.Stat() } 68 69 func (f *fakeFile) Stat() (fs.FileInfo, error) { 70 switch f.typ { 71 case ftypeRoot: 72 return stat{name: ".", mode: fs.ModeDir}, nil 73 case ftypeIssueDir, ftypeCommentDir: 74 return stat{name: f.Name(), mode: 0o555 | fs.ModeDir}, nil 75 case ftypeIssue: 76 issue, err := f.Client.LookupIssue(f.owner, f.repo, f.number) 77 if err != nil { 78 return nil, fmt.Errorf("lookup issue %d: %w", f.number, err) 79 } 80 buf := &bytes.Buffer{} 81 if f.buf == nil { 82 f.buf = buf 83 } 84 _ = printIssue(buf, issue) // cannot fail writing to Buffer 85 st := stat{ 86 name: f.Name(), 87 mtime: issue.Updated, 88 length: int64(buf.Len()), 89 sys: issue, 90 } 91 return st, nil 92 case ftypeComment: 93 comment, err := f.Client.LookupComment(f.owner, f.repo, f.number) 94 if err != nil { 95 return nil, &fs.PathError{"stat", f.Name(), fmt.Errorf("lookup comment %d: %w", f.number, err)} 96 } 97 buf := &bytes.Buffer{} 98 if f.buf == nil { 99 f.buf = buf 100 } 101 _ = printComment(buf, *comment) 102 st := stat{ 103 name: f.Name(), 104 mtime: comment.Updated, 105 length: int64(buf.Len()), 106 sys: comment, 107 } 108 return st, nil 109 } 110 return nil, &fs.PathError{"stat", f.Name(), fs.ErrNotExist} 111 } 112 113 func (f *fakeFile) Read(p []byte) (n int, err error) { 114 switch f.typ { 115 case ftypeRoot, ftypeIssueDir, ftypeCommentDir: 116 return 0, &fs.PathError{"read", f.Name(), fmt.Errorf("is a directory")} 117 } 118 119 if f.buf == nil { 120 if _, err := f.Stat(); err != nil { 121 return 0, &fs.PathError{"read", f.Name(), err} 122 } 123 } 124 return f.buf.Read(p) 125 } 126 127 func (f *fakeFile) ReadDir(n int) ([]fs.DirEntry, error) { 128 if f.typ == ftypeIssue || f.typ == ftypeComment { 129 return nil, &fs.PathError{"readdir", f.Name(), fmt.Errorf("not a directory")} 130 } 131 132 if f.children == nil { 133 switch f.typ { 134 case ftypeRoot: 135 issues, err := f.Client.Issues(f.owner, f.repo) 136 if err != nil { 137 return nil, &fs.PathError{"readdir", f.Name(), err} 138 } 139 f.children = make([]fs.DirEntry, len(issues)) 140 for i, issue := range issues { 141 f.children[i] = &fakeFile{ 142 typ: ftypeIssueDir, 143 number: issue.Number, 144 } 145 } 146 case ftypeIssueDir: 147 f.children = []fs.DirEntry{ 148 &fakeFile{ 149 typ: ftypeIssue, 150 number: f.number, 151 }, 152 &fakeFile{ 153 typ: ftypeCommentDir, 154 number: f.number, 155 }, 156 } 157 case ftypeCommentDir: 158 comments, err := f.Client.Comments(f.owner, f.repo, f.number) 159 if err != nil { 160 return nil, &fs.PathError{"readdir", f.Name(), err} 161 } 162 f.children = make([]fs.DirEntry, len(comments)) 163 for i, c := range comments { 164 f.children[i] = &fakeFile{ 165 typ: ftypeComment, 166 number: c.ID, 167 } 168 } 169 } 170 } 171 172 if f.dirp >= len(f.children) { 173 if n <= 0 { 174 return nil, nil 175 } 176 return nil, io.EOF 177 } 178 if n <= 0 { 179 f.dirp = len(f.children) 180 return f.children, nil 181 } 182 183 var err error 184 d := f.children[f.dirp:] 185 if len(d) >= n { 186 d = d[:n] 187 } else if len(d) <= n { 188 err = io.EOF 189 } 190 f.dirp += n 191 return d, err 192 } 193 194 func (f *fakeFile) Close() error { 195 if f.buf != nil { 196 f.buf.Reset() 197 } 198 f.buf = nil 199 f.children = nil 200 f.Client = nil 201 return nil 202 } 203 204 type stat struct { 205 name string 206 length int64 207 mode fs.FileMode 208 mtime time.Time 209 sys any 210 } 211 212 func (s stat) Name() string { return s.name } 213 func (s stat) Size() int64 { return s.length } 214 func (s stat) Mode() fs.FileMode { return s.mode } 215 func (s stat) ModTime() time.Time { return s.mtime } 216 func (s stat) IsDir() bool { return s.Mode().IsDir() } 217 func (s stat) Sys() any { return s.sys }