Monorepo for Tangled tangled.org
5

Configure Feed

Select the types of activity you want to include in your feed.

1package migrate 2 3import ( 4 "context" 5 "database/sql" 6 "errors" 7 "fmt" 8 "log/slog" 9 "os" 10 "path/filepath" 11 "strings" 12 13 "github.com/bluesky-social/indigo/atproto/syntax" 14 "tangled.org/core/api/tangled" 15 "tangled.org/core/knotmirror/db" 16) 17 18type Stats struct { 19 Renamed int 20 Skipped int 21 Orphaned int 22 OwnerDirsRm int 23 AlreadyExists int 24} 25 26func (s Stats) String() string { 27 return fmt.Sprintf( 28 "renamed=%d skipped=%d orphaned=%d owner_dirs_removed=%d target_existed=%d", 29 s.Renamed, s.Skipped, s.Orphaned, s.OwnerDirsRm, s.AlreadyExists, 30 ) 31} 32 33func RenameDisk(ctx context.Context, base string, database *sql.DB, logger *slog.Logger) (Stats, error) { 34 entries, err := os.ReadDir(base) 35 if err != nil { 36 return Stats{}, fmt.Errorf("reading base path: %w", err) 37 } 38 return reduceEntries(ctx, entries, 0, Stats{}, ownerStep(base, database, logger)) 39} 40 41type stepFn func(context.Context, os.DirEntry, Stats) (Stats, error) 42 43func reduceEntries(ctx context.Context, entries []os.DirEntry, idx int, acc Stats, fn stepFn) (Stats, error) { 44 if idx >= len(entries) { 45 return acc, nil 46 } 47 if err := ctx.Err(); err != nil { 48 return acc, err 49 } 50 next, err := fn(ctx, entries[idx], acc) 51 if err != nil { 52 return next, err 53 } 54 return reduceEntries(ctx, entries, idx+1, next, fn) 55} 56 57func ownerStep(base string, database *sql.DB, logger *slog.Logger) stepFn { 58 return func(ctx context.Context, entry os.DirEntry, acc Stats) (Stats, error) { 59 if !entry.IsDir() || !strings.HasPrefix(entry.Name(), "did:") { 60 return acc, nil 61 } 62 ownerPath := filepath.Join(base, entry.Name()) 63 if _, err := os.Stat(filepath.Join(ownerPath, "HEAD")); err == nil { 64 return acc, nil 65 } 66 subEntries, err := os.ReadDir(ownerPath) 67 if err != nil { 68 logger.Error("reading owner dir", "ownerPath", ownerPath, "err", err) 69 return acc, nil 70 } 71 next, err := reduceEntries(ctx, subEntries, 0, acc, rkeyStep(base, database, logger, syntax.DID(entry.Name()), ownerPath)) 72 if err != nil { 73 return next, err 74 } 75 remaining, err := os.ReadDir(ownerPath) 76 if err == nil && len(remaining) == 0 { 77 if rmErr := os.Remove(ownerPath); rmErr == nil { 78 next.OwnerDirsRm++ 79 logger.Info("removed empty owner dir", "ownerPath", ownerPath) 80 } else { 81 logger.Warn("failed to remove empty owner dir", "ownerPath", ownerPath, "err", rmErr) 82 } 83 } 84 return next, nil 85 } 86} 87 88func rkeyStep(base string, database *sql.DB, logger *slog.Logger, ownerDid syntax.DID, ownerPath string) stepFn { 89 return func(ctx context.Context, sub os.DirEntry, acc Stats) (Stats, error) { 90 if !sub.IsDir() { 91 return acc, nil 92 } 93 rkey := sub.Name() 94 subPath := filepath.Join(ownerPath, rkey) 95 l := logger.With("did", ownerDid, "rkey", rkey, "subPath", subPath) 96 97 if _, err := os.Stat(filepath.Join(subPath, "HEAD")); err != nil { 98 l.Warn("skipping non-repo subdir") 99 acc.Skipped++ 100 return acc, nil 101 } 102 103 aturi := syntax.ATURI(fmt.Sprintf("at://%s/%s/%s", ownerDid, tangled.RepoNSID, rkey)) 104 repo, err := db.GetRepoByAtUri(ctx, database, aturi) 105 if err != nil { 106 return acc, fmt.Errorf("looking up repo by aturi %s: %w", aturi, err) 107 } 108 if repo == nil { 109 l.Warn("orphan disk repo, no DB row; leaving in place") 110 acc.Orphaned++ 111 return acc, nil 112 } 113 if repo.RepoDid == "" { 114 l.Warn("DB row has empty repo_did; leaving in place") 115 acc.Orphaned++ 116 return acc, nil 117 } 118 119 target := filepath.Join(base, repo.RepoDid.String()) 120 if _, err := os.Stat(target); err == nil { 121 l.Warn("target path already exists; leaving source in place", "target", target) 122 acc.AlreadyExists++ 123 return acc, nil 124 } else if !errors.Is(err, os.ErrNotExist) { 125 return acc, fmt.Errorf("stat target %s: %w", target, err) 126 } 127 128 if err := os.Rename(subPath, target); err != nil { 129 return acc, fmt.Errorf("rename %s -> %s: %w", subPath, target, err) 130 } 131 acc.Renamed++ 132 l.Info("renamed", "target", target) 133 return acc, nil 134 } 135}