Paperclip/pkg/internal/services/attachments.go

162 lines
3.8 KiB
Go
Raw Normal View History

2024-05-17 07:59:51 +00:00
package services
import (
"fmt"
"mime"
"mime/multipart"
"net/http"
"path/filepath"
2024-07-25 14:02:26 +00:00
"git.solsynth.dev/hydrogen/paperclip/pkg/internal/database"
2024-06-22 04:18:54 +00:00
"git.solsynth.dev/hydrogen/paperclip/pkg/internal/models"
2024-05-17 07:59:51 +00:00
"github.com/google/uuid"
"gorm.io/gorm"
)
2024-06-29 13:16:11 +00:00
const metadataCacheLimit = 512
var metadataCache = make(map[uint]models.Attachment)
2024-05-17 07:59:51 +00:00
func GetAttachmentByID(id uint) (models.Attachment, error) {
2024-06-29 13:16:11 +00:00
if val, ok := metadataCache[id]; ok {
return val, nil
2024-05-17 07:59:51 +00:00
}
var attachment models.Attachment
if err := database.C.Where(models.Attachment{
2024-06-29 13:16:11 +00:00
BaseModel: models.BaseModel{ID: id},
2024-07-25 17:29:16 +00:00
}).Preload("Account").First(&attachment).Error; err != nil {
2024-05-17 07:59:51 +00:00
return attachment, err
2024-06-29 13:16:11 +00:00
} else {
if len(metadataCache) > metadataCacheLimit {
clear(metadataCache)
}
metadataCache[id] = attachment
2024-05-17 07:59:51 +00:00
}
2024-06-29 13:16:11 +00:00
2024-05-17 07:59:51 +00:00
return attachment, nil
}
func GetAttachmentByHash(hash string) (models.Attachment, error) {
var attachment models.Attachment
if err := database.C.Where(models.Attachment{
HashCode: hash,
}).First(&attachment).Error; err != nil {
return attachment, err
}
return attachment, nil
}
func NewAttachmentMetadata(tx *gorm.DB, user *models.Account, file *multipart.FileHeader, attachment models.Attachment) (models.Attachment, error) {
attachment.Uuid = uuid.NewString()
attachment.Size = file.Size
attachment.Name = file.Filename
attachment.AccountID = user.ID
// If the user didn't provide file mimetype manually, we have to detect it
if len(attachment.MimeType) == 0 {
if ext := filepath.Ext(attachment.Name); len(ext) > 0 {
// Detect mimetype by file extensions
attachment.MimeType = mime.TypeByExtension(ext)
} else {
// Detect mimetype by file header
// This method as a fallback method, because this isn't pretty accurate
header, err := file.Open()
if err != nil {
return attachment, fmt.Errorf("failed to read file header: %v", err)
}
defer header.Close()
fileHeader := make([]byte, 512)
_, err = header.Read(fileHeader)
if err != nil {
return attachment, err
2024-05-17 07:59:51 +00:00
}
attachment.MimeType = http.DetectContentType(fileHeader)
2024-05-17 07:59:51 +00:00
}
}
if err := tx.Save(&attachment).Error; err != nil {
return attachment, fmt.Errorf("failed to save attachment record: %v", err)
2024-06-29 13:16:11 +00:00
} else {
if len(metadataCache) > metadataCacheLimit {
clear(metadataCache)
}
metadataCache[attachment.ID] = attachment
2024-05-17 07:59:51 +00:00
}
return attachment, nil
}
func TryLinkAttachment(tx *gorm.DB, og models.Attachment, hash string) (bool, error) {
prev, err := GetAttachmentByHash(hash)
if err != nil {
return false, err
}
prev.RefCount++
og.RefID = &prev.ID
og.Uuid = prev.Uuid
og.Destination = prev.Destination
if err := tx.Save(&og).Error; err != nil {
tx.Rollback()
return true, err
} else if err = tx.Save(&prev).Error; err != nil {
tx.Rollback()
return true, err
}
metadataCache[prev.ID] = prev
metadataCache[og.ID] = og
return true, nil
2024-05-17 07:59:51 +00:00
}
2024-06-29 13:16:11 +00:00
func UpdateAttachment(item models.Attachment) (models.Attachment, error) {
if err := database.C.Save(&item).Error; err != nil {
return item, err
} else {
if len(metadataCache) > metadataCacheLimit {
clear(metadataCache)
}
metadataCache[item.ID] = item
}
return item, nil
}
2024-05-17 07:59:51 +00:00
func DeleteAttachment(item models.Attachment) error {
dat := item
tx := database.C.Begin()
if item.RefID != nil {
var refTarget models.Attachment
if err := database.C.Where(models.Attachment{
BaseModel: models.BaseModel{ID: *item.RefID},
}).First(&refTarget).Error; err == nil {
refTarget.RefCount--
if err := tx.Save(&refTarget).Error; err != nil {
tx.Rollback()
return fmt.Errorf("unable to update ref count: %v", err)
}
}
2024-05-17 07:59:51 +00:00
}
if err := database.C.Delete(&item).Error; err != nil {
tx.Rollback()
2024-05-17 07:59:51 +00:00
return err
2024-06-29 13:16:11 +00:00
} else {
delete(metadataCache, item.ID)
2024-05-17 07:59:51 +00:00
}
tx.Commit()
if dat.RefCount == 0 {
2024-07-28 16:53:40 +00:00
PublishDeleteFileTask(dat)
2024-05-17 07:59:51 +00:00
}
return nil
}