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 _, err := strg.Put(path.Join("objects", p.RelativePath()), content)
47 return err
48 })
49 })
50 }
51
52 var batch []lfs.Pointer
53 for pointer := range pointerChan {
54 obj, err := store.GetLFSObjectByOid(ctx, dbx, repo.ID(), pointer.Oid)
55 if err != nil && !errors.Is(err, db.ErrRecordNotFound) {
56 return db.WrapError(err)
57 }
58
59 exist, err := strg.Exists(path.Join("objects", pointer.RelativePath()))
60 if err != nil {
61 return err
62 }
63
64 if exist && obj.ID == 0 {
65 if err := store.CreateLFSObject(ctx, dbx, repo.ID(), pointer.Oid, pointer.Size); err != nil {
66 return db.WrapError(err)
67 }
68 } else {
69 batch = append(batch, pointer.Pointer)
70 // Limit batch requests to 20 objects
71 if len(batch) >= 20 {
72 if err := download(batch); err != nil {
73 return err
74 }
75
76 batch = nil
77 }
78 }
79 }
80
81 if err, ok := <-errChan; ok {
82 return err
83 }
84
85 return nil
86}