collection.go (4322B)
1 package store 2 3 import ( 4 "encoding/json" 5 "fmt" 6 "os" 7 "path/filepath" 8 "sync" 9 "time" 10 11 "code.dwrz.net/src/pkg/log" 12 ) 13 14 type Collection struct { 15 log *log.Logger 16 path string 17 name string 18 } 19 20 func (s *Store) NewCollection(name string) error { 21 var c = &Collection{ 22 log: s.log, 23 name: name, 24 path: filepath.Join(s.path, name), 25 } 26 27 if _, err := os.Stat(c.path); os.IsNotExist(err) { 28 if err := os.MkdirAll(c.path, os.ModeDir|fm); err != nil { 29 return fmt.Errorf( 30 "failed to create directory: %w", err, 31 ) 32 } 33 } 34 35 s.mu.Lock() 36 s.collections[c.name] = c 37 s.mu.Unlock() 38 39 return nil 40 } 41 42 func (s *Store) Collection(name string) *Collection { 43 s.mu.Lock() 44 defer s.mu.Unlock() 45 46 return s.collections[name] 47 } 48 49 func (c *Collection) All() ([]*Document, error) { 50 names, err := c.List() 51 if err != nil { 52 return nil, fmt.Errorf("failed to list: %w", err) 53 } 54 55 var ( 56 wg sync.WaitGroup 57 58 mu sync.Mutex 59 errs []error 60 docs []*Document 61 ) 62 for _, name := range names { 63 wg.Add(1) 64 65 go func(name string) { 66 defer wg.Done() 67 68 d, err := c.FindId(name) 69 if err != nil { 70 mu.Lock() 71 errs = append(errs, fmt.Errorf( 72 "failed to load document: %v", err, 73 )) 74 mu.Unlock() 75 76 return 77 } 78 79 mu.Lock() 80 docs = append(docs, d) 81 mu.Unlock() 82 }(name) 83 } 84 85 wg.Wait() 86 87 if len(errs) > 0 { 88 for _, err := range errs { 89 c.log.Error.Print(err) 90 } 91 92 return nil, fmt.Errorf("failed to load all quotes") 93 } 94 95 return docs, nil 96 } 97 98 func (c *Collection) Count() (int, error) { 99 f, err := os.Open(c.path) 100 if err != nil { 101 return 0, fmt.Errorf("failed to open: %w", err) 102 } 103 defer f.Close() 104 105 entries, err := f.ReadDir(0) 106 if err != nil { 107 return 0, fmt.Errorf("failed to read directory: %w", err) 108 } 109 110 var count int 111 for _, e := range entries { 112 // Nested collections are not allowed. 113 if e.IsDir() { 114 continue 115 } 116 117 // Ignore any files without our file extension. 118 if !hasExt(e.Name()) { 119 continue 120 } 121 122 count++ 123 } 124 125 return count, nil 126 } 127 128 func (c *Collection) FindId(id string) (*Document, error) { 129 if !hasExt(id) { 130 id += ext 131 } 132 133 path := filepath.Join(c.path, id) 134 data, err := os.ReadFile(path) 135 if err != nil { 136 return nil, fmt.Errorf("failed to read file %s: %w", path, err) 137 } 138 139 var d = &Document{path: path} 140 if err := json.Unmarshal(data, d); err != nil { 141 return nil, fmt.Errorf( 142 "failed to json unmarshal document %s: %w", path, err, 143 ) 144 } 145 146 return d, nil 147 } 148 149 func (c *Collection) List() ([]string, error) { 150 entries, err := os.ReadDir(c.path) 151 if err != nil { 152 return nil, fmt.Errorf("failed to read directory: %w", err) 153 } 154 155 var names []string 156 for _, e := range entries { 157 // Nested collections are not allowed. 158 if e.IsDir() { 159 continue 160 } 161 162 // Ignore any files without a JSON file extension. 163 var name = e.Name() 164 if !hasExt(name) { 165 continue 166 } 167 168 names = append(names, name) 169 } 170 171 return names, nil 172 } 173 174 func (c *Collection) Create(id string, v any) (*Document, error) { 175 data, err := json.MarshalIndent(v, "", " ") 176 if err != nil { 177 return nil, fmt.Errorf("failed to json marshal: %w", err) 178 } 179 180 now := time.Now() 181 var d = &Document{ 182 Created: now, 183 Data: data, 184 Id: id, 185 Updated: now, 186 } 187 188 data, err = json.MarshalIndent(d, "", " ") 189 if err != nil { 190 return nil, fmt.Errorf("failed to json marshal: %w", err) 191 } 192 193 path := filepath.Join(c.path, d.Id+ext) 194 if err := os.WriteFile(path, data, fm); err != nil { 195 return nil, fmt.Errorf("failed to write file %s: %w", path, err) 196 } 197 198 return d, nil 199 } 200 201 func (c *Collection) Delete(id string) error { 202 var path = filepath.Join(c.path, id) 203 if err := os.Remove(path); err != nil { 204 return fmt.Errorf("failed to remove file: %v", err) 205 } 206 207 return nil 208 } 209 210 func (c *Collection) Random() (*Document, error) { 211 entries, err := os.ReadDir(c.path) 212 if err != nil { 213 return nil, fmt.Errorf("failed to read directory: %w", err) 214 } 215 if len(entries) == 0 { 216 return nil, fmt.Errorf("empty collection") 217 } 218 219 name := entries[random.Intn(len(entries))].Name() 220 path := filepath.Join(c.path, name) 221 data, err := os.ReadFile(path) 222 if err != nil { 223 return nil, fmt.Errorf("failed to read file %s: %w", path, err) 224 } 225 226 var d = &Document{ 227 path: path, 228 } 229 if err := json.Unmarshal(data, d); err != nil { 230 return nil, fmt.Errorf( 231 "failed to json unmarshal document %s: %w", path, err, 232 ) 233 } 234 235 return d, nil 236 }