github.com/Azareal/Gosora@v0.0.0-20210729070923-553e66b59003/common/attachments.go (about) 1 package common 2 3 import ( 4 "database/sql" 5 "errors" 6 7 //"fmt" 8 "os" 9 "path/filepath" 10 "strings" 11 12 qgen "github.com/Azareal/Gosora/query_gen" 13 ) 14 15 var Attachments AttachmentStore 16 17 var ErrCorruptAttachPath = errors.New("corrupt attachment path") 18 19 type MiniAttachment struct { 20 ID int 21 SectionID int 22 OriginID int 23 UploadedBy int 24 Path string 25 Extra string 26 27 Image bool 28 Ext string 29 } 30 31 type Attachment struct { 32 ID int 33 SectionTable string 34 SectionID int 35 OriginTable string 36 OriginID int 37 UploadedBy int 38 Path string 39 Extra string 40 41 Image bool 42 Ext string 43 } 44 45 type AttachmentStore interface { 46 GetForRenderRoute(filename string, sid int, sectionTable string) (*Attachment, error) 47 FGet(id int) (*Attachment, error) 48 Get(id int) (*MiniAttachment, error) 49 MiniGetList(originTable string, originID int) (alist []*MiniAttachment, err error) 50 BulkMiniGetList(originTable string, ids []int) (amap map[int][]*MiniAttachment, err error) 51 Add(sectionID int, sectionTable string, originID int, originTable string, uploadedBy int, path, extra string) (int, error) 52 MoveTo(sectionID, originID int, originTable string) error 53 MoveToByExtra(sectionID int, originTable, extra string) error 54 Count() int 55 CountIn(originTable string, oid int) int 56 CountInPath(path string) int 57 Delete(id int) error 58 59 AddLinked(otable string, oid int) (err error) 60 RemoveLinked(otable string, oid int) (err error) 61 } 62 63 type DefaultAttachmentStore struct { 64 getForRenderRoute *sql.Stmt 65 66 fget *sql.Stmt 67 get *sql.Stmt 68 getByObj *sql.Stmt 69 add *sql.Stmt 70 count *sql.Stmt 71 countIn *sql.Stmt 72 countInPath *sql.Stmt 73 move *sql.Stmt 74 moveByExtra *sql.Stmt 75 delete *sql.Stmt 76 77 replyUpdateAttachs *sql.Stmt 78 topicUpdateAttachs *sql.Stmt 79 } 80 81 func NewDefaultAttachmentStore(acc *qgen.Accumulator) (*DefaultAttachmentStore, error) { 82 a := "attachments" 83 return &DefaultAttachmentStore{ 84 getForRenderRoute: acc.Select(a).Columns("sectionTable, originID, originTable, uploadedBy, path").Where("path=? AND sectionID=? AND sectionTable=?").Prepare(), 85 86 fget: acc.Select(a).Columns("originTable, originID, sectionTable, sectionID, uploadedBy, path, extra").Where("attachID=?").Prepare(), 87 get: acc.Select(a).Columns("originID, sectionID, uploadedBy, path, extra").Where("attachID=?").Prepare(), 88 getByObj: acc.Select(a).Columns("attachID, sectionID, uploadedBy, path, extra").Where("originTable=? AND originID=?").Prepare(), 89 add: acc.Insert(a).Columns("sectionID, sectionTable, originID, originTable, uploadedBy, path, extra").Fields("?,?,?,?,?,?,?").Prepare(), 90 count: acc.Count(a).Prepare(), 91 countIn: acc.Count(a).Where("originTable=? and originID=?").Prepare(), 92 countInPath: acc.Count(a).Where("path=?").Prepare(), 93 move: acc.Update(a).Set("sectionID=?").Where("originID=? AND originTable=?").Prepare(), 94 moveByExtra: acc.Update(a).Set("sectionID=?").Where("originTable=? AND extra=?").Prepare(), 95 delete: acc.Delete(a).Where("attachID=?").Prepare(), 96 97 // TODO: Less race-y attachment count updates 98 replyUpdateAttachs: acc.Update("replies").Set("attachCount=?").Where("rid=?").Prepare(), 99 topicUpdateAttachs: acc.Update("topics").Set("attachCount=?").Where("tid=?").Prepare(), 100 }, acc.FirstError() 101 } 102 103 // TODO: Revamp this to make it less of a copy-paste from the original code in the route 104 // ! Lacks some attachment initialisation code 105 func (s *DefaultAttachmentStore) GetForRenderRoute(filename string, sid int, sectionTable string) (*Attachment, error) { 106 a := &Attachment{SectionID: sid} 107 e := s.getForRenderRoute.QueryRow(filename, sid, sectionTable).Scan(&a.SectionTable, &a.OriginID, &a.OriginTable, &a.UploadedBy, &a.Path) 108 // TODO: Initialise attachment struct fields? 109 return a, e 110 } 111 112 func (s *DefaultAttachmentStore) MiniGetList(originTable string, originID int) (alist []*MiniAttachment, err error) { 113 rows, err := s.getByObj.Query(originTable, originID) 114 defer rows.Close() 115 for rows.Next() { 116 a := &MiniAttachment{OriginID: originID} 117 err := rows.Scan(&a.ID, &a.SectionID, &a.UploadedBy, &a.Path, &a.Extra) 118 if err != nil { 119 return nil, err 120 } 121 a.Ext = strings.TrimPrefix(filepath.Ext(a.Path), ".") 122 if len(a.Ext) == 0 { 123 return nil, ErrCorruptAttachPath 124 } 125 a.Image = ImageFileExts.Contains(a.Ext) 126 alist = append(alist, a) 127 } 128 if err = rows.Err(); err != nil { 129 return nil, err 130 } 131 if len(alist) == 0 { 132 err = sql.ErrNoRows 133 } 134 return alist, err 135 } 136 137 func (s *DefaultAttachmentStore) BulkMiniGetList(originTable string, ids []int) (amap map[int][]*MiniAttachment, err error) { 138 if len(ids) == 0 { 139 return nil, sql.ErrNoRows 140 } 141 if len(ids) == 1 { 142 res, err := s.MiniGetList(originTable, ids[0]) 143 return map[int][]*MiniAttachment{ids[0]: res}, err 144 } 145 146 amap = make(map[int][]*MiniAttachment) 147 var buffer []*MiniAttachment 148 var currentID int 149 rows, err := qgen.NewAcc().Select("attachments").Columns("attachID,sectionID,originID,uploadedBy,path").Where("originTable=?").In("originID", ids).Orderby("originID ASC").Query(originTable) 150 defer rows.Close() 151 for rows.Next() { 152 a := &MiniAttachment{} 153 err := rows.Scan(&a.ID, &a.SectionID, &a.OriginID, &a.UploadedBy, &a.Path) 154 if err != nil { 155 return nil, err 156 } 157 a.Ext = strings.TrimPrefix(filepath.Ext(a.Path), ".") 158 if len(a.Ext) == 0 { 159 return nil, ErrCorruptAttachPath 160 } 161 a.Image = ImageFileExts.Contains(a.Ext) 162 if currentID == 0 { 163 currentID = a.OriginID 164 } 165 if a.OriginID != currentID { 166 if len(buffer) > 0 { 167 amap[currentID] = buffer 168 currentID = a.OriginID 169 buffer = nil 170 } 171 } 172 buffer = append(buffer, a) 173 } 174 if len(buffer) > 0 { 175 amap[currentID] = buffer 176 } 177 return amap, rows.Err() 178 } 179 180 func (s *DefaultAttachmentStore) FGet(id int) (*Attachment, error) { 181 a := &Attachment{ID: id} 182 e := s.fget.QueryRow(id).Scan(&a.OriginTable, &a.OriginID, &a.SectionTable, &a.SectionID, &a.UploadedBy, &a.Path, &a.Extra) 183 if e != nil { 184 return nil, e 185 } 186 a.Ext = strings.TrimPrefix(filepath.Ext(a.Path), ".") 187 if len(a.Ext) == 0 { 188 return nil, ErrCorruptAttachPath 189 } 190 a.Image = ImageFileExts.Contains(a.Ext) 191 return a, nil 192 } 193 194 func (s *DefaultAttachmentStore) Get(id int) (*MiniAttachment, error) { 195 a := &MiniAttachment{ID: id} 196 err := s.get.QueryRow(id).Scan(&a.OriginID, &a.SectionID, &a.UploadedBy, &a.Path, &a.Extra) 197 if err != nil { 198 return nil, err 199 } 200 a.Ext = strings.TrimPrefix(filepath.Ext(a.Path), ".") 201 if len(a.Ext) == 0 { 202 return nil, ErrCorruptAttachPath 203 } 204 a.Image = ImageFileExts.Contains(a.Ext) 205 return a, nil 206 } 207 208 func (s *DefaultAttachmentStore) Add(sectionID int, sectionTable string, originID int, originTable string, uploadedBy int, path, extra string) (int, error) { 209 res, err := s.add.Exec(sectionID, sectionTable, originID, originTable, uploadedBy, path, extra) 210 if err != nil { 211 return 0, err 212 } 213 lid, err := res.LastInsertId() 214 return int(lid), err 215 } 216 217 func (s *DefaultAttachmentStore) MoveTo(sectionID, originID int, originTable string) error { 218 _, err := s.move.Exec(sectionID, originID, originTable) 219 return err 220 } 221 222 func (s *DefaultAttachmentStore) MoveToByExtra(sectionID int, originTable, extra string) error { 223 _, err := s.moveByExtra.Exec(sectionID, originTable, extra) 224 return err 225 } 226 227 func (s *DefaultAttachmentStore) Count() (count int) { 228 e := s.count.QueryRow().Scan(&count) 229 if e != nil { 230 LogError(e) 231 } 232 return count 233 } 234 235 func (s *DefaultAttachmentStore) CountIn(originTable string, oid int) (count int) { 236 e := s.countIn.QueryRow(originTable, oid).Scan(&count) 237 if e != nil { 238 LogError(e) 239 } 240 return count 241 } 242 243 func (s *DefaultAttachmentStore) CountInPath(path string) (count int) { 244 e := s.countInPath.QueryRow(path).Scan(&count) 245 if e != nil { 246 LogError(e) 247 } 248 return count 249 } 250 251 func (s *DefaultAttachmentStore) Delete(id int) error { 252 _, e := s.delete.Exec(id) 253 return e 254 } 255 256 // TODO: Split this out of this store 257 func (s *DefaultAttachmentStore) AddLinked(otable string, oid int) (err error) { 258 switch otable { 259 case "topics": 260 _, err = s.topicUpdateAttachs.Exec(s.CountIn(otable, oid), oid) 261 if err != nil { 262 return err 263 } 264 err = Topics.Reload(oid) 265 case "replies": 266 _, err = s.replyUpdateAttachs.Exec(s.CountIn(otable, oid), oid) 267 if err != nil { 268 return err 269 } 270 err = Rstore.GetCache().Remove(oid) 271 } 272 if err == sql.ErrNoRows { 273 err = nil 274 } 275 return err 276 } 277 278 // TODO: Split this out of this store 279 func (s *DefaultAttachmentStore) RemoveLinked(otable string, oid int) (err error) { 280 switch otable { 281 case "topics": 282 _, err = s.topicUpdateAttachs.Exec(s.CountIn(otable, oid), oid) 283 if err != nil { 284 return err 285 } 286 if tc := Topics.GetCache(); tc != nil { 287 tc.Remove(oid) 288 } 289 case "replies": 290 _, err = s.replyUpdateAttachs.Exec(s.CountIn(otable, oid), oid) 291 if err != nil { 292 return err 293 } 294 err = Rstore.GetCache().Remove(oid) 295 } 296 return err 297 } 298 299 // TODO: Add a table for the files and lock the file row when performing tasks related to the file 300 func DeleteAttachment(aid int) error { 301 a, err := Attachments.FGet(aid) 302 if err != nil { 303 return err 304 } 305 err = deleteAttachment(a) 306 if err != nil { 307 return err 308 } 309 _ = Attachments.RemoveLinked(a.OriginTable, a.OriginID) 310 return nil 311 } 312 313 func deleteAttachment(a *Attachment) error { 314 err := Attachments.Delete(a.ID) 315 if err != nil { 316 return err 317 } 318 319 count := Attachments.CountInPath(a.Path) 320 if count == 0 { 321 err := os.Remove("./attachs/" + a.Path) 322 if err != nil { 323 return err 324 } 325 } 326 327 return nil 328 }