forgejo/services/f3/driver/attachment.go
limiting-factor a71392c6aa chore: update gof3/v3 v3.11.15 (#10673)
Update the Forgejo driver for gof3 with modifications for non-backward compatible changes. The changes are isolated behind the f3.Enable flag and not yet functional. The purpose of this upgrade is to not drift from the gof3 implementation while the work continues.

The `fix: include remote users when counting users` commit is a functional change to Forgejo itself but does not change the behavior because the remote users are only created in fixtures or by F3.

59c721d26b/models/user/user.go (L65-L66)

Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/10673
Reviewed-by: Gusted <gusted@noreply.codeberg.org>
Co-authored-by: limiting-factor <limiting-factor@posteo.com>
Co-committed-by: limiting-factor <limiting-factor@posteo.com>
2026-01-13 16:59:56 +01:00

186 lines
4.9 KiB
Go

// Copyright Earl Warren <contact@earl-warren.org>
// Copyright Loïc Dachary <loic@dachary.org>
// SPDX-License-Identifier: MIT
package driver
import (
"context"
"crypto/sha256"
"encoding/hex"
"fmt"
"io"
"os"
"forgejo.org/models/db"
repo_model "forgejo.org/models/repo"
user_model "forgejo.org/models/user"
"forgejo.org/modules/storage"
"forgejo.org/modules/timeutil"
attachment_service "forgejo.org/services/attachment"
"code.forgejo.org/f3/gof3/v3/f3"
f3_id "code.forgejo.org/f3/gof3/v3/id"
f3_kind "code.forgejo.org/f3/gof3/v3/kind"
f3_tree "code.forgejo.org/f3/gof3/v3/tree/f3"
f3_tree_generic "code.forgejo.org/f3/gof3/v3/tree/generic"
f3_util "code.forgejo.org/f3/gof3/v3/util"
"github.com/google/uuid"
)
var _ f3_tree.ForgeDriverInterface = &issue{}
type attachment struct {
common
forgejoAttachment *repo_model.Attachment
sha string
contentType string
downloadFunc f3.DownloadFuncType
}
func (o *attachment) SetNative(attachment any) {
o.forgejoAttachment = attachment.(*repo_model.Attachment)
}
func (o *attachment) GetNativeID() string {
return fmt.Sprintf("%d", o.forgejoAttachment.ID)
}
func (o *attachment) NewFormat() f3.Interface {
node := o.GetNode()
return node.GetTree().(f3_tree.TreeInterface).NewFormat(node.GetKind())
}
func (o *attachment) ToFormat() f3.Interface {
if o.forgejoAttachment == nil {
return o.NewFormat()
}
return &f3.Attachment{
Common: f3.NewCommon(o.GetNativeID()),
Name: o.forgejoAttachment.Name,
ContentType: o.contentType,
Size: o.forgejoAttachment.Size,
DownloadCount: o.forgejoAttachment.DownloadCount,
Created: o.forgejoAttachment.CreatedUnix.AsTime(),
SHA256: o.sha,
DownloadURL: o.forgejoAttachment.DownloadURL(),
DownloadFunc: o.downloadFunc,
}
}
func (o *attachment) FromFormat(content f3.Interface) {
attachment := content.(*f3.Attachment)
o.forgejoAttachment = &repo_model.Attachment{
ID: f3_util.ParseInt(attachment.GetID()),
Name: attachment.Name,
Size: attachment.Size,
DownloadCount: attachment.DownloadCount,
CreatedUnix: timeutil.TimeStamp(attachment.Created.Unix()),
CustomDownloadURL: attachment.DownloadURL,
}
o.contentType = attachment.ContentType
o.sha = attachment.SHA256
o.downloadFunc = attachment.DownloadFunc
}
func (o *attachment) Get(ctx context.Context) bool {
node := o.GetNode()
o.Trace("%s", node.GetID())
id := node.GetID().Int64()
attachment, err := repo_model.GetAttachmentByID(ctx, id)
if repo_model.IsErrAttachmentNotExist(err) {
return false
}
if err != nil {
panic(fmt.Errorf("attachment %v %w", id, err))
}
o.forgejoAttachment = attachment
path := o.forgejoAttachment.RelativePath()
{
f, err := storage.Attachments.Open(path)
if err != nil {
panic(err)
}
hasher := sha256.New()
if _, err := io.Copy(hasher, f); err != nil {
panic(fmt.Errorf("io.Copy to hasher: %v", err))
}
o.sha = hex.EncodeToString(hasher.Sum(nil))
}
o.downloadFunc = func() io.ReadCloser {
o.Trace("download %s from copy stored in temporary file %s", o.forgejoAttachment.DownloadURL, path)
f, err := os.Open(path)
if err != nil {
panic(err)
}
return f
}
return true
}
func (o *attachment) Patch(ctx context.Context) {
o.Trace("%d", o.forgejoAttachment.ID)
if _, err := db.GetEngine(ctx).ID(o.forgejoAttachment.ID).Cols("name").Update(o.forgejoAttachment); err != nil {
panic(fmt.Errorf("UpdateAttachmentCols: %v %v", o.forgejoAttachment, err))
}
}
func (o *attachment) Put(ctx context.Context) f3_id.NodeID {
node := o.GetNode()
o.Trace("%s", node.GetID())
uploader, err := user_model.GetAdminUser(ctx)
if err != nil {
panic(fmt.Errorf("GetAdminUser %w", err))
}
attachable := f3_tree.GetAttachable(o.GetNode())
attachableID := f3_tree.GetAttachableID(o.GetNode())
switch attachable.GetKind() {
case f3_kind.KindRelease:
o.forgejoAttachment.ReleaseID = attachableID
case f3_kind.KindComment:
o.forgejoAttachment.CommentID = attachableID
case f3_kind.KindIssue, f3_kind.KindPullRequest:
o.forgejoAttachment.IssueID = attachableID
default:
panic(fmt.Errorf("unexpected type %s", attachable.GetKind()))
}
o.forgejoAttachment.UploaderID = uploader.ID
o.forgejoAttachment.RepoID = f3_tree.GetProjectID(o.GetNode())
o.forgejoAttachment.UUID = uuid.New().String()
download := o.downloadFunc()
defer download.Close()
_, err = attachment_service.NewAttachment(ctx, o.forgejoAttachment, download, o.forgejoAttachment.Size)
if err != nil {
panic(err)
}
o.Trace("attachment created %d", o.forgejoAttachment.ID)
return f3_id.NewNodeID(o.forgejoAttachment.ID)
}
func (o *attachment) Delete(ctx context.Context) {
node := o.GetNode()
o.Trace("%s", node.GetID())
if err := repo_model.DeleteAttachment(ctx, o.forgejoAttachment, true); err != nil {
panic(err)
}
}
func newAttachment() f3_tree_generic.NodeDriverInterface {
return &attachment{}
}