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}