master
go 262 lines 5.31 KB
Raw
1 // Excluded from plan9 (no fsnotify support).
2 //go:build !plan9
3
4 package main
5
6 import (
7 "context"
8 "flag"
9 "log"
10 "os"
11 "os/signal"
12 "path/filepath"
13 "slices"
14 "syscall"
15
16 commands "github.com/ipfs/kubo/commands"
17 "github.com/ipfs/kubo/config"
18 core "github.com/ipfs/kubo/core"
19 coreapi "github.com/ipfs/kubo/core/coreapi"
20 corehttp "github.com/ipfs/kubo/core/corehttp"
21 "github.com/ipfs/kubo/misc/fsutil"
22 "github.com/ipfs/kubo/plugin"
23 pluginbadgerds "github.com/ipfs/kubo/plugin/plugins/badgerds"
24 pluginflatfs "github.com/ipfs/kubo/plugin/plugins/flatfs"
25 pluginlevelds "github.com/ipfs/kubo/plugin/plugins/levelds"
26 pluginpebbleds "github.com/ipfs/kubo/plugin/plugins/pebbleds"
27 fsrepo "github.com/ipfs/kubo/repo/fsrepo"
28
29 fsnotify "github.com/fsnotify/fsnotify"
30 "github.com/ipfs/boxo/files"
31 )
32
33 var (
34 http = flag.Bool("http", false, "expose IPFS HTTP API")
35 repoPath *string
36 watchPath = flag.String("path", ".", "the path to watch")
37 )
38
39 func init() {
40 ipfsPath, err := config.PathRoot()
41 if err != nil {
42 ipfsPath = os.Getenv(config.EnvDir)
43 }
44 repoPath = flag.String("repo", ipfsPath, "repo path to use")
45 }
46
47 func main() {
48 flag.Parse()
49
50 // precedence
51 // 1. --repo flag
52 // 2. IPFS_PATH environment variable
53 // 3. default repo path
54 var ipfsPath string
55 if *repoPath != "" {
56 ipfsPath = *repoPath
57 } else {
58 var err error
59 ipfsPath, err = fsrepo.BestKnownPath()
60 if err != nil {
61 log.Fatal(err)
62 }
63 }
64
65 if err := run(ipfsPath, *watchPath); err != nil {
66 log.Fatal(err)
67 }
68 }
69
70 func loadDatastorePlugins(plugins []plugin.Plugin) error {
71 for _, pl := range plugins {
72 if pl, ok := pl.(plugin.PluginDatastore); ok {
73 err := fsrepo.AddDatastoreConfigHandler(pl.DatastoreTypeName(), pl.DatastoreConfigParser())
74 if err != nil {
75 return err
76 }
77 }
78 }
79 return nil
80 }
81
82 func run(ipfsPath, watchPath string) error {
83 log.Printf("running IPFSWatch on '%s' using repo at '%s'...", watchPath, ipfsPath)
84
85 ipfsPath, err := fsutil.ExpandHome(ipfsPath)
86 if err != nil {
87 return err
88 }
89 watcher, err := fsnotify.NewWatcher()
90 if err != nil {
91 return err
92 }
93 defer watcher.Close()
94
95 if err := addTree(watcher, watchPath); err != nil {
96 return err
97 }
98
99 if err = loadDatastorePlugins(slices.Concat(
100 pluginbadgerds.Plugins,
101 pluginflatfs.Plugins,
102 pluginlevelds.Plugins,
103 pluginpebbleds.Plugins,
104 )); err != nil {
105 return err
106 }
107
108 r, err := fsrepo.Open(ipfsPath)
109 if err != nil {
110 // TODO handle case: daemon running
111 // TODO handle case: repo doesn't exist or isn't initialized
112 return err
113 }
114
115 node, err := core.NewNode(context.Background(), &core.BuildCfg{
116 Online: true,
117 Repo: r,
118 })
119 if err != nil {
120 return err
121 }
122 defer node.Close()
123
124 api, err := coreapi.NewCoreAPI(node)
125 if err != nil {
126 return err
127 }
128
129 if *http {
130 addr := "/ip4/127.0.0.1/tcp/5001"
131 opts := []corehttp.ServeOption{
132 corehttp.GatewayOption("/ipfs", "/ipns"),
133 corehttp.WebUIOption,
134 corehttp.CommandsOption(cmdCtx(node, ipfsPath)),
135 }
136 go func() {
137 if err := corehttp.ListenAndServe(node, addr, opts...); err != nil {
138 return
139 }
140 }()
141 }
142
143 interrupts := make(chan os.Signal, 1)
144 signal.Notify(interrupts, os.Interrupt, syscall.SIGTERM)
145
146 for {
147 select {
148 case <-interrupts:
149 return nil
150 case e := <-watcher.Events:
151 log.Printf("received event: %s", e)
152 isDir, err := IsDirectory(e.Name)
153 if err != nil {
154 log.Println(err)
155 continue
156 }
157 switch e.Op {
158 case fsnotify.Remove:
159 if isDir {
160 if err := watcher.Remove(e.Name); err != nil {
161 return err
162 }
163 }
164 default:
165 // all events except for Remove result in an IPFS.Add, but only
166 // directory creation triggers a new watch
167 switch e.Op {
168 case fsnotify.Create:
169 if isDir {
170 if err := addTree(watcher, e.Name); err != nil {
171 return err
172 }
173 }
174 }
175 go func() {
176 file, err := os.Open(e.Name)
177 if err != nil {
178 log.Println(err)
179 return
180 }
181 defer file.Close()
182
183 st, err := file.Stat()
184 if err != nil {
185 log.Println(err)
186 return
187 }
188
189 f, err := files.NewReaderPathFile(e.Name, file, st)
190 if err != nil {
191 log.Println(err)
192 return
193 }
194
195 k, err := api.Unixfs().Add(node.Context(), f)
196 if err != nil {
197 log.Println(err)
198 }
199 log.Printf("added %s... key: %s", e.Name, k)
200 }()
201 }
202 case err := <-watcher.Errors:
203 log.Println(err)
204 }
205 }
206 }
207
208 func addTree(w *fsnotify.Watcher, root string) error {
209 err := filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
210 if err != nil {
211 log.Println(err)
212 return nil
213 }
214 isDir, err := IsDirectory(path)
215 if err != nil {
216 log.Println(err)
217 return nil
218 }
219 switch {
220 case isDir && IsHidden(path):
221 log.Println(path)
222 return filepath.SkipDir
223 case isDir:
224 log.Println(path)
225 if err = w.Add(path); err != nil {
226 return err
227 }
228 default:
229 return nil
230 }
231 return nil
232 })
233 return err
234 }
235
236 func IsDirectory(path string) (bool, error) {
237 fileInfo, err := os.Stat(path)
238 if err != nil {
239 return false, err
240 }
241 return fileInfo.IsDir(), nil
242 }
243
244 func IsHidden(path string) bool {
245 path = filepath.Base(path)
246 if path == "." || path == "" {
247 return false
248 }
249 if rune(path[0]) == rune('.') {
250 return true
251 }
252 return false
253 }
254
255 func cmdCtx(node *core.IpfsNode, repoPath string) commands.Context {
256 return commands.Context{
257 ConfigRoot: repoPath,
258 ConstructNode: func() (*core.IpfsNode, error) {
259 return node, nil
260 },
261 }
262 }