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