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