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  }