mirror of
https://github.com/hanzoai/forge.git
synced 2026-10-30 15:00:39 +00:00
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]>
154 lines
3.7 KiB
Go
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
|
|
}
|