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
|
|
|
|
}
|
|
|
|
|
2024-07-28 16:01:51 +00:00
|
|
|
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
|
|
|
}
|
2024-07-28 16:01:51 +00:00
|
|
|
attachment.MimeType = http.DetectContentType(fileHeader)
|
2024-05-17 07:59:51 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := tx.Save(&attachment).Error; err != nil {
|
2024-07-28 16:01:51 +00:00
|
|
|
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
|
|
|
}
|
|
|
|
|
2024-07-28 16:01: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 {
|
2024-07-28 16:01:51 +00:00
|
|
|
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 {
|
2024-07-28 16:01:51 +00:00
|
|
|
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
|
|
|
}
|
|
|
|
|
2024-07-28 16:01: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
|
|
|
|
}
|