1
package commands
2
3
import (
4
+ "context"
5
"errors"
6
"fmt"
7
"io"
9
gopath "path"
10
"strconv"
11
"strings"
12
+ "time"
13
14
"github.com/ipfs/kubo/config"
15
"github.com/ipfs/kubo/core/commands/cmdenv"
63
inlineLimitOptionName = "inline-limit"
64
toFilesOptionName = "to-files"
65
64
- preserveModeOptionName = "preserve-mode"
65
- preserveMtimeOptionName = "preserve-mtime"
66
- modeOptionName = "mode"
67
- mtimeOptionName = "mtime"
68
- mtimeNsecsOptionName = "mtime-nsecs"
66
+ preserveModeOptionName = "preserve-mode"
67
+ preserveMtimeOptionName = "preserve-mtime"
68
+ modeOptionName = "mode"
69
+ mtimeOptionName = "mtime"
70
+ mtimeNsecsOptionName = "mtime-nsecs"
71
+ fastProvideRootOptionName = "fast-provide-root"
72
+ fastProvideWaitOptionName = "fast-provide-wait"
73
)
74
71
-const adderOutChanSize = 8
75
+const (
76
+ adderOutChanSize = 8
77
+
78
+ // fastProvideTimeout is the maximum time allowed for async fast-provide operations.
79
+ // Prevents hanging on network issues when providing root CID in background.
80
+ // 10 seconds is sufficient for DHT operations with sweep provider or accelerated client.
81
+ fastProvideTimeout = 10 * time.Second
82
+)
83
84
var AddCmd = &cmds.Command{
85
Helptext: cmds.HelpText{
86
Tagline: "Add a file or directory to IPFS.",
87
ShortDescription: `
88
Adds the content of <path> to IPFS. Use -r to add directories (recursively).
89
+
90
+FAST PROVIDE OPTIMIZATION:
91
+
92
+When you add content to IPFS, it gets queued for announcement on the DHT.
93
+The background queue can take some time to process, meaning other peers
94
+won't find your content immediately after 'ipfs add' completes.
95
+
96
+To make sharing faster, 'ipfs add' does an extra immediate announcement
97
+of just the root CID to the DHT. This lets other peers start discovering
98
+your content right away, while the regular background queue still handles
99
+announcing all the blocks later.
100
+
101
+By default, this extra announcement runs in the background without slowing
102
+down the command. If you need to be certain the root CID is discoverable
103
+before the command returns (for example, sharing a link immediately),
104
+use --fast-provide-wait to wait for the announcement to complete.
105
+Use --fast-provide-root=false to skip this optimization and rely only on
106
+the background queue (controlled by Provide.Strategy and Provide.DHT.Interval).
107
+
108
+This works best with the sweep provider and accelerated DHT client.
109
+Automatically skipped when DHT is not available.
110
`,
111
LongDescription: `
112
Adds the content of <path> to IPFS. Use -r to add directories.
245
cmds.UintOption(modeOptionName, "Custom POSIX file mode to store in created UnixFS entries. WARNING: experimental, forces dag-pb for root block, disables raw-leaves"),
246
cmds.Int64Option(mtimeOptionName, "Custom POSIX modification time to store in created UnixFS entries (seconds before or after the Unix Epoch). WARNING: experimental, forces dag-pb for root block, disables raw-leaves"),
247
cmds.UintOption(mtimeNsecsOptionName, "Custom POSIX modification time (optional time fraction in nanoseconds)"),
248
+ cmds.BoolOption(fastProvideRootOptionName, "Immediately provide root CID to DHT for fast content discovery. When disabled, root CID is queued for background providing instead.").WithDefault(true),
249
+ cmds.BoolOption(fastProvideWaitOptionName, "Wait for fast-provide-root to complete before returning. Ensures root CID is discoverable when command finishes.").WithDefault(false),
250
},
251
PreRun: func(req *cmds.Request, env cmds.Environment) error {
252
quiet, _ := req.Options[quietOptionName].(bool)
317
mode, _ := req.Options[modeOptionName].(uint)
318
mtime, _ := req.Options[mtimeOptionName].(int64)
319
mtimeNsecs, _ := req.Options[mtimeNsecsOptionName].(uint)
320
+ fastProvideRoot, _ := req.Options[fastProvideRootOptionName].(bool)
321
+ fastProvideWait, _ := req.Options[fastProvideWaitOptionName].(bool)
322
323
if chunker == "" {
324
chunker = cfg.Import.UnixFSChunker.WithDefault(config.DefaultUnixFSChunker)
457
}
458
var added int
459
var fileAddedToMFS bool
460
+ var lastRootCid path.ImmutablePath // Track the root CID for fast-provide
461
addit := toadd.Entries()
462
for addit.Next() {
463
_, dir := addit.Node().(files.Directory)
464
errCh := make(chan error, 1)
428
- events := make(chan interface{}, adderOutChanSize)
465
+ events := make(chan any, adderOutChanSize)
466
opts[len(opts)-1] = options.Unixfs.Events(events)
467
468
go func() {
474
return
475
}
476
477
+ // Store the root CID for potential fast-provide operation
478
+ lastRootCid = pathAdded
479
+
480
// creating MFS pointers when optional --to-files is set
481
if toFilesSet {
482
if addit.Name() == "" {
600
return fmt.Errorf("expected a file argument")
601
}
602
603
+ // Apply fast-provide-root if the flag is enabled
604
+ if fastProvideRoot && (lastRootCid != path.ImmutablePath{}) {
605
+ cfg, err := ipfsNode.Repo.Config()
606
+ if err != nil {
607
+ return err
608
+ }
609
+
610
+ // Parse the provide strategy to check if we should provide based on pin/MFS status
611
+ strategyStr := cfg.Provide.Strategy.WithDefault(config.DefaultProvideStrategy)
612
+ strategy := config.ParseProvideStrategy(strategyStr)
613
+
614
+ // Determine if we should provide based on strategy
615
+ shouldProvide := false
616
+ if strategy == config.ProvideStrategyAll {
617
+ // 'all' strategy: always provide
618
+ shouldProvide = true
619
+ } else {
620
+ // For combined strategies (pinned+mfs), check each component
621
+ if strategy&config.ProvideStrategyPinned != 0 && dopin {
622
+ shouldProvide = true
623
+ } else if strategy&config.ProvideStrategyRoots != 0 && dopin {
624
+ shouldProvide = true
625
+ } else if strategy&config.ProvideStrategyMFS != 0 && toFilesSet {
626
+ shouldProvide = true
627
+ }
628
+ }
629
+
630
+ switch {
631
+ case !cfg.Provide.Enabled.WithDefault(config.DefaultProvideEnabled):
632
+ log.Debugw("fast-provide-root: skipped", "reason", "Provide.Enabled is false")
633
+ case cfg.Provide.DHT.Interval.WithDefault(config.DefaultProvideDHTInterval) == 0:
634
+ log.Debugw("fast-provide-root: skipped", "reason", "Provide.DHT.Interval is 0")
635
+ case !shouldProvide:
636
+ log.Debugw("fast-provide-root: skipped", "reason", "strategy does not match content", "strategy", strategyStr, "pinned", dopin, "to-files", toFilesSet)
637
+ case !ipfsNode.HasActiveDHTClient():
638
+ log.Debugw("fast-provide-root: skipped", "reason", "DHT not available")
639
+ default:
640
+ rootCid := lastRootCid.RootCid()
641
+
642
+ if fastProvideWait {
643
+ // Synchronous mode: block until provide completes
644
+ log.Debugw("fast-provide-root: providing synchronously", "cid", rootCid)
645
+ if err := provideCIDSync(req.Context, ipfsNode.DHTClient, rootCid); err != nil {
646
+ log.Warnw("fast-provide-root: sync provide failed", "cid", rootCid, "error", err)
647
+ } else {
648
+ log.Debugw("fast-provide-root: sync provide completed", "cid", rootCid)
649
+ }
650
+ } else {
651
+ // Asynchronous mode (default): fire-and-forget, don't block
652
+ log.Debugw("fast-provide-root: providing asynchronously", "cid", rootCid)
653
+ go func() {
654
+ // Use detached context with timeout to prevent hanging on network issues
655
+ ctx, cancel := context.WithTimeout(context.Background(), fastProvideTimeout)
656
+ defer cancel()
657
+ if err := provideCIDSync(ctx, ipfsNode.DHTClient, rootCid); err != nil {
658
+ log.Warnw("fast-provide-root: async provide failed", "cid", rootCid, "error", err)
659
+ } else {
660
+ log.Debugw("fast-provide-root: async provide completed", "cid", rootCid)
661
+ }
662
+ }()
663
+ }
664
+ }
665
+ } else if fastProvideWait && !fastProvideRoot {
666
+ // Log that wait flag is ignored when provide-root is disabled
667
+ log.Debugw("fast-provide-root: wait flag ignored", "reason", "fast-provide-root disabled")
668
+ }
669
+
670
return nil
671
},
672
PostRun: cmds.PostRunMap{
673
cmds.CLI: func(res cmds.Response, re cmds.ResponseEmitter) error {
674
sizeChan := make(chan int64, 1)
568
- outChan := make(chan interface{})
675
+ outChan := make(chan any)
676
req := res.Request()
677
678
// Could be slow.