Blob


1 package fs
3 import (
4 "bytes"
5 "fmt"
6 "io"
7 "io/fs"
8 "strings"
9 "time"
11 "olowe.co/apub/lemmy"
12 )
14 type fakeStat struct {
15 name string
16 size int64
17 mode fs.FileMode
18 mtime time.Time
19 }
21 func (s *fakeStat) Name() string { return s.name }
22 func (s *fakeStat) Size() int64 { return s.size }
23 func (s *fakeStat) Mode() fs.FileMode { return s.mode }
24 func (s *fakeStat) ModTime() time.Time { return s.mtime }
25 func (s *fakeStat) IsDir() bool { return s.mode.IsDir() }
26 func (s *fakeStat) Sys() any { return nil }
28 type dummy struct {
29 name string
30 mode fs.FileMode
31 mtime time.Time
32 contents []byte
33 dirinfo *dirInfo
34 buf io.ReadCloser
35 }
37 func (f *dummy) Name() string { return f.name }
38 func (f *dummy) IsDir() bool { return f.mode.IsDir() }
39 func (f *dummy) Type() fs.FileMode { return f.mode.Type() }
40 func (f *dummy) Info() (fs.FileInfo, error) { return f.Stat() }
42 func (f *dummy) Stat() (fs.FileInfo, error) {
43 return &fakeStat{
44 name: f.name,
45 mode: f.mode,
46 size: int64(len(f.contents)),
47 mtime: f.mtime,
48 }, nil
49 }
51 func (f *dummy) Read(p []byte) (int, error) {
52 if f.buf == nil {
53 f.buf = io.NopCloser(bytes.NewReader(f.contents))
54 }
55 return f.buf.Read(p)
56 }
58 func (f *dummy) Close() error {
59 if f.buf == nil {
60 return nil
61 }
62 err := f.buf.Close()
63 f.buf = nil
64 return err
65 }
67 func (f *dummy) ReadDir(n int) ([]fs.DirEntry, error) {
68 if !f.mode.IsDir() {
69 return nil, &fs.PathError{"readdir", f.name, fmt.Errorf("not a directory")}
70 } else if f.dirinfo == nil {
71 // TODO(otl): is this accidental? maybe return an error here.
72 return nil, &fs.PathError{"readdir", f.name, fmt.Errorf("no dirinfo to track reads")}
73 }
75 return f.dirinfo.ReadDir(n)
76 }
78 type lFile struct {
79 info fs.FileInfo
80 dirinfo *dirInfo
81 client *lemmy.Client
82 buf io.ReadCloser
83 }
85 func (f *lFile) Read(p []byte) (int, error) {
86 if f.buf == nil {
87 f.buf = io.NopCloser(strings.NewReader("directory"))
88 }
89 return f.buf.Read(p)
90 }
92 func (f *lFile) Close() error {
93 if f.buf == nil || f.dirinfo == nil {
94 return fs.ErrClosed
95 }
96 f.dirinfo = nil
97 err := f.buf.Close()
98 f.buf = nil
99 return err
102 func (f *lFile) Stat() (fs.FileInfo, error) {
103 return f.info, nil
106 func (f *lFile) ReadDir(n int) ([]fs.DirEntry, error) {
107 if f.dirinfo == nil {
108 f.dirinfo = new(dirInfo)
109 switch f.info.(type) {
110 case *lemmy.Community:
111 posts, err := f.client.Posts(f.info.Name(), lemmy.ListAll)
112 if err != nil {
113 return nil, &fs.PathError{"readdir", f.info.Name(), err}
115 for _, p := range posts {
116 p := p
117 f.dirinfo.entries = append(f.dirinfo.entries, fs.FileInfoToDirEntry(&p))
119 case *lemmy.Post:
120 p := f.info.(*lemmy.Post)
121 comments, err := f.client.Comments(p.ID, lemmy.ListAll)
122 if err != nil {
123 return nil, &fs.PathError{"readdir", f.info.Name(), err}
125 for _, c := range comments {
126 c := c
127 f.dirinfo.entries = append(f.dirinfo.entries, fs.FileInfoToDirEntry(&c))
129 f.dirinfo.entries = append(f.dirinfo.entries, postFile(p))
130 default:
131 return nil, &fs.PathError{"readdir", f.info.Name(), fmt.Errorf("not a directory")}
134 return f.dirinfo.ReadDir(n)
136 func postText(p *lemmy.Post) *bytes.Buffer {
137 buf := &bytes.Buffer{}
138 fmt.Fprintln(buf, "From:", p.CreatorID)
139 fmt.Fprintf(buf, "Message-Id: <%d>\n", p.ID)
140 fmt.Fprintf(buf, "List-Archive: <%s>\n", p.URL)
141 fmt.Fprintln(buf, "Date:", p.ModTime().Format(time.RFC822))
142 fmt.Fprintln(buf, "Subject:", p.Title)
143 fmt.Fprintln(buf)
144 if p.URL != "" {
145 fmt.Fprintln(buf, p.URL)
147 fmt.Fprintln(buf, p.Body)
148 return buf
151 func postFile(p *lemmy.Post) *dummy {
152 return &dummy{
153 name: "post",
154 mode: 0o444,
155 mtime: p.ModTime(),
156 contents: postText(p).Bytes(),
160 func commentText(c *lemmy.Comment) *bytes.Buffer {
161 buf := &bytes.Buffer{}
162 fmt.Fprintln(buf, "From:", c.CreatorID)
163 fmt.Fprintln(buf, "Date:", c.ModTime().Format(time.RFC822))
164 fmt.Fprintf(buf, "Message-ID: <%d>\n", c.ID)
165 fmt.Fprintf(buf, "List-Archive: <%s>\n", c.ActivityURL)
166 fmt.Fprintln(buf, "Subject: Re:", c.PostID)
167 fmt.Fprintln(buf)
168 fmt.Fprintln(buf, c.Content)
169 return buf