Files
forge/models/repo/upload.go
zeekayandHanzo Dev aa87442fdd Record upstream authorship centrally in NOTICE instead of in every file header
Source files keep their SPDX-License-Identifier and Hanzo copyright line; the
Gitea, Gogs and LFS Test Server copyrights they used to repeat now live once in
NOTICE, alongside LICENSE. The header lint accepts any copyright line above the
SPDX line, so it no longer demands an upstream one.

Co-authored-by: Hanzo Dev <[email protected]>
2026-09-10 17:50:35 -07:00

154 lines
3.7 KiB
Go

// Copyright 2026 Hanzo AI, Inc. All rights reserved.
// SPDX-License-Identifier: MIT
package repo
import (
"context"
"fmt"
"io"
"mime/multipart"
"os"
"path/filepath"
"github.com/hanzoai/git/models/db"
"github.com/hanzoai/git/modules/log"
"github.com/hanzoai/git/modules/setting"
"github.com/hanzoai/git/modules/util"
gouuid "github.com/google/uuid"
)
// ErrUploadNotExist represents a "UploadNotExist" kind of error.
type ErrUploadNotExist struct {
ID int64
UUID string
}
// IsErrUploadNotExist checks if an error is a ErrUploadNotExist.
func IsErrUploadNotExist(err error) bool {
_, ok := err.(ErrUploadNotExist)
return ok
}
func (err ErrUploadNotExist) Error() string {
return fmt.Sprintf("attachment does not exist [id: %d, uuid: %s]", err.ID, err.UUID)
}
func (err ErrUploadNotExist) Unwrap() error {
return util.ErrNotExist
}
// Upload represent a uploaded file to a repo to be deleted when moved
type Upload struct {
ID int64 `xorm:"pk autoincr"`
UUID string `xorm:"uuid UNIQUE"`
Name string
}
func init() {
db.RegisterModel(new(Upload))
}
// LocalPath returns where uploads are temporarily stored in local file system based on given UUID.
func (upload *Upload) LocalPath() string {
uuid := upload.UUID
return setting.AppDataTempDir("repo-uploads").JoinPath(uuid[0:1], uuid[1:2], uuid)
}
// NewUpload creates a new upload object.
func NewUpload(ctx context.Context, name string, buf []byte, file multipart.File) (_ *Upload, err error) {
upload := &Upload{
UUID: gouuid.New().String(),
Name: name,
}
localPath := upload.LocalPath()
if err = os.MkdirAll(filepath.Dir(localPath), os.ModePerm); err != nil {
return nil, fmt.Errorf("MkdirAll: %w", err)
}
fw, err := os.Create(localPath)
if err != nil {
return nil, fmt.Errorf("Create: %w", err)
}
defer fw.Close()
if _, err = fw.Write(buf); err != nil {
return nil, fmt.Errorf("Write: %w", err)
} else if _, err = io.Copy(fw, file); err != nil {
return nil, fmt.Errorf("Copy: %w", err)
}
if _, err := db.GetEngine(ctx).Insert(upload); err != nil {
return nil, err
}
return upload, nil
}
// GetUploadByUUID returns the Upload by UUID
func GetUploadByUUID(ctx context.Context, uuid string) (*Upload, error) {
upload := &Upload{}
has, err := db.GetEngine(ctx).Where("uuid=?", uuid).Get(upload)
if err != nil {
return nil, err
} else if !has {
return nil, ErrUploadNotExist{0, uuid}
}
return upload, nil
}
// GetUploadsByUUIDs returns multiple uploads by UUIDS
func GetUploadsByUUIDs(ctx context.Context, uuids []string) ([]*Upload, error) {
if len(uuids) == 0 {
return []*Upload{}, nil
}
// Silently drop invalid uuids.
uploads := make([]*Upload, 0, len(uuids))
return uploads, db.GetEngine(ctx).In("uuid", uuids).Find(&uploads)
}
// DeleteUploads deletes multiple uploads
func DeleteUploads(ctx context.Context, uploads ...*Upload) (err error) {
if len(uploads) == 0 {
return nil
}
ids := make([]int64, len(uploads))
for i := range uploads {
ids[i] = uploads[i].ID
}
if err = db.DeleteByIDs[Upload](ctx, ids...); err != nil {
return fmt.Errorf("delete uploads: %w", err)
}
for _, upload := range uploads {
localPath := upload.LocalPath()
if err := util.Remove(localPath); err != nil {
// just continue, don't fail the whole operation if a file is missing (removed by others)
log.Error("unable to remove upload file %s: %v", localPath, err)
}
}
return nil
}
// DeleteUploadByUUID deletes a upload by UUID
func DeleteUploadByUUID(ctx context.Context, uuid string) error {
upload, err := GetUploadByUUID(ctx, uuid)
if err != nil {
if IsErrUploadNotExist(err) {
return nil
}
return fmt.Errorf("GetUploadByUUID: %w", err)
}
if err := DeleteUploads(ctx, upload); err != nil {
return fmt.Errorf("DeleteUpload: %w", err)
}
return nil
}