lfs.go

 1package backend
 2
 3import (
 4	"context"
 5	"errors"
 6	"io"
 7	"path"
 8	"path/filepath"
 9
10	"github.com/charmbracelet/soft-serve/server/config"
11	"github.com/charmbracelet/soft-serve/server/db"
12	"github.com/charmbracelet/soft-serve/server/lfs"
13	"github.com/charmbracelet/soft-serve/server/proto"
14	"github.com/charmbracelet/soft-serve/server/storage"
15	"github.com/charmbracelet/soft-serve/server/store"
16)
17
18// StoreRepoMissingLFSObjects stores missing LFS objects for a repository.
19func StoreRepoMissingLFSObjects(ctx context.Context, repo proto.Repository, dbx *db.DB, store store.Store, lfsClient lfs.Client) error {
20	cfg := config.FromContext(ctx)
21	lfsRoot := filepath.Join(cfg.DataPath, "lfs")
22
23	// TODO: support S3 storage
24	strg := storage.NewLocalStorage(lfsRoot)
25	pointerChan := make(chan lfs.PointerBlob)
26	errChan := make(chan error, 1)
27	r, err := repo.Open()
28	if err != nil {
29		return err
30	}
31
32	go lfs.SearchPointerBlobs(ctx, r, pointerChan, errChan)
33
34	download := func(pointers []lfs.Pointer) error {
35		return lfsClient.Download(ctx, pointers, func(p lfs.Pointer, content io.ReadCloser, objectError error) error {
36			if objectError != nil {
37				return objectError
38			}
39
40			defer content.Close() // nolint: errcheck
41			return dbx.TransactionContext(ctx, func(tx *db.Tx) error {
42				if err := store.CreateLFSObject(ctx, tx, repo.ID(), p.Oid, p.Size); err != nil {
43					return db.WrapError(err)
44				}
45
46				return strg.Put(path.Join("objects", p.RelativePath()), content)
47			})
48		})
49	}
50
51	var batch []lfs.Pointer
52	for pointer := range pointerChan {
53		obj, err := store.GetLFSObjectByOid(ctx, dbx, repo.ID(), pointer.Oid)
54		if err != nil && !errors.Is(err, db.ErrRecordNotFound) {
55			return db.WrapError(err)
56		}
57
58		exist, err := strg.Exists(path.Join("objects", pointer.RelativePath()))
59		if err != nil {
60			return err
61		}
62
63		if exist && obj.ID == 0 {
64			if err := store.CreateLFSObject(ctx, dbx, repo.ID(), pointer.Oid, pointer.Size); err != nil {
65				return db.WrapError(err)
66			}
67		} else {
68			batch = append(batch, pointer.Pointer)
69			// Limit batch requests to 20 objects
70			if len(batch) >= 20 {
71				if err := download(batch); err != nil {
72					return err
73				}
74
75				batch = nil
76			}
77		}
78	}
79
80	if err, ok := <-errChan; ok {
81		return err
82	}
83
84	return nil
85}