]> Sergey Matveev's repositories - btrtrc.git/blob - cmd/torrentfs/main.go
gofumpt
[btrtrc.git] / cmd / torrentfs / main.go
1 // Mounts a FUSE filesystem backed by torrents and magnet links.
2 package main
3
4 import (
5         "fmt"
6         "net"
7         "net/http"
8         _ "net/http/pprof"
9         "os"
10         "os/signal"
11         "os/user"
12         "path/filepath"
13         "syscall"
14         "time"
15
16         "bazil.org/fuse"
17         fusefs "bazil.org/fuse/fs"
18         _ "github.com/anacrolix/envpprof"
19         "github.com/anacrolix/log"
20         "github.com/anacrolix/tagflag"
21
22         "github.com/anacrolix/torrent"
23         torrentfs "github.com/anacrolix/torrent/fs"
24         "github.com/anacrolix/torrent/util/dirwatch"
25 )
26
27 var args = struct {
28         MetainfoDir string `help:"torrent files in this location describe the contents of the mounted filesystem"`
29         DownloadDir string `help:"location to save torrent data"`
30         MountDir    string `help:"location the torrent contents are made available"`
31
32         DisableTrackers bool
33         TestPeer        *net.TCPAddr
34         ReadaheadBytes  tagflag.Bytes
35         ListenAddr      *net.TCPAddr
36 }{
37         MetainfoDir: func() string {
38                 _user, err := user.Current()
39                 if err != nil {
40                         panic(err)
41                 }
42                 return filepath.Join(_user.HomeDir, ".config/transmission/torrents")
43         }(),
44         ReadaheadBytes: 10 << 20,
45         ListenAddr:     &net.TCPAddr{},
46 }
47
48 func exitSignalHandlers(fs *torrentfs.TorrentFS) {
49         c := make(chan os.Signal, 1)
50         signal.Notify(c, syscall.SIGINT, syscall.SIGTERM)
51         for {
52                 <-c
53                 fs.Destroy()
54                 err := fuse.Unmount(args.MountDir)
55                 if err != nil {
56                         log.Print(err)
57                 }
58         }
59 }
60
61 func addTestPeer(client *torrent.Client) {
62         for _, t := range client.Torrents() {
63                 t.AddPeers([]torrent.PeerInfo{{
64                         Addr: args.TestPeer,
65                 }})
66         }
67 }
68
69 func main() {
70         err := mainErr()
71         if err != nil {
72                 log.Printf("error in main: %v", err)
73                 os.Exit(1)
74         }
75 }
76
77 func mainErr() error {
78         tagflag.Parse(&args)
79         if args.MountDir == "" {
80                 os.Stderr.WriteString("y u no specify mountpoint?\n")
81                 os.Exit(2)
82         }
83         conn, err := fuse.Mount(args.MountDir)
84         if err != nil {
85                 return fmt.Errorf("mounting: %w", err)
86         }
87         defer fuse.Unmount(args.MountDir)
88         // TODO: Think about the ramifications of exiting not due to a signal.
89         defer conn.Close()
90         cfg := torrent.NewDefaultClientConfig()
91         cfg.DataDir = args.DownloadDir
92         cfg.DisableTrackers = args.DisableTrackers
93         cfg.NoUpload = true // Ensure that downloads are responsive.
94         cfg.SetListenAddr(args.ListenAddr.String())
95         client, err := torrent.NewClient(cfg)
96         if err != nil {
97                 return fmt.Errorf("creating torrent client: %w", err)
98         }
99         // This is naturally exported via GOPPROF=http.
100         http.DefaultServeMux.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) {
101                 client.WriteStatus(w)
102         })
103         dw, err := dirwatch.New(args.MetainfoDir)
104         if err != nil {
105                 return fmt.Errorf("watching torrent dir: %w", err)
106         }
107         dw.Logger = dw.Logger.FilterLevel(log.Info)
108         go func() {
109                 for ev := range dw.Events {
110                         switch ev.Change {
111                         case dirwatch.Added:
112                                 if ev.TorrentFilePath != "" {
113                                         _, err := client.AddTorrentFromFile(ev.TorrentFilePath)
114                                         if err != nil {
115                                                 log.Printf("error adding torrent from file %q to client: %v", ev.TorrentFilePath, err)
116                                         }
117                                 } else if ev.MagnetURI != "" {
118                                         _, err := client.AddMagnet(ev.MagnetURI)
119                                         if err != nil {
120                                                 log.Printf("error adding magnet: %s", err)
121                                         }
122                                 }
123                         case dirwatch.Removed:
124                                 T, ok := client.Torrent(ev.InfoHash)
125                                 if !ok {
126                                         break
127                                 }
128                                 T.Drop()
129                         }
130                 }
131         }()
132         fs := torrentfs.New(client)
133         go exitSignalHandlers(fs)
134
135         if args.TestPeer != nil {
136                 go func() {
137                         for {
138                                 addTestPeer(client)
139                                 time.Sleep(10 * time.Second)
140                         }
141                 }()
142         }
143
144         if err := fusefs.Serve(conn, fs); err != nil {
145                 return fmt.Errorf("serving fuse fs: %w", err)
146         }
147         <-conn.Ready
148         if err := conn.MountError; err != nil {
149                 return fmt.Errorf("mount error: %w", err)
150         }
151         return nil
152 }