1 // Mounts a FUSE filesystem backed by torrents and magnet links.
16 "github.com/anacrolix/envpprof"
17 _ "github.com/anacrolix/envpprof"
18 "github.com/anacrolix/fuse"
19 fusefs "github.com/anacrolix/fuse/fs"
20 "github.com/anacrolix/log"
21 "github.com/anacrolix/tagflag"
23 "github.com/anacrolix/torrent"
24 torrentfs "github.com/anacrolix/torrent/fs"
25 "github.com/anacrolix/torrent/util/dirwatch"
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"`
35 ReadaheadBytes tagflag.Bytes
36 ListenAddr *net.TCPAddr
38 MetainfoDir: func() string {
39 _user, err := user.Current()
43 return filepath.Join(_user.HomeDir, ".config/transmission/torrents")
45 ReadaheadBytes: 10 << 20,
46 ListenAddr: &net.TCPAddr{},
49 func exitSignalHandlers(fs *torrentfs.TorrentFS) {
50 c := make(chan os.Signal, 1)
51 signal.Notify(c, syscall.SIGINT, syscall.SIGTERM)
55 err := fuse.Unmount(args.MountDir)
62 func addTestPeer(client *torrent.Client) {
63 for _, t := range client.Torrents() {
64 t.AddPeers([]torrent.PeerInfo{{
74 log.Printf("error in main: %v", err)
79 func mainErr() error {
81 if args.MountDir == "" {
82 os.Stderr.WriteString("y u no specify mountpoint?\n")
85 conn, err := fuse.Mount(args.MountDir)
87 return fmt.Errorf("mounting: %w", err)
89 defer fuse.Unmount(args.MountDir)
90 // TODO: Think about the ramifications of exiting not due to a signal.
92 cfg := torrent.NewDefaultClientConfig()
93 cfg.DataDir = args.DownloadDir
94 cfg.DisableTrackers = args.DisableTrackers
95 cfg.NoUpload = true // Ensure that downloads are responsive.
96 cfg.SetListenAddr(args.ListenAddr.String())
97 client, err := torrent.NewClient(cfg)
99 return fmt.Errorf("creating torrent client: %w", err)
101 // This is naturally exported via GOPPROF=http.
102 http.DefaultServeMux.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) {
103 client.WriteStatus(w)
105 dw, err := dirwatch.New(args.MetainfoDir)
107 return fmt.Errorf("watching torrent dir: %w", err)
109 dw.Logger = dw.Logger.FilterLevel(log.Info)
111 for ev := range dw.Events {
114 if ev.TorrentFilePath != "" {
115 _, err := client.AddTorrentFromFile(ev.TorrentFilePath)
117 log.Printf("error adding torrent from file %q to client: %v", ev.TorrentFilePath, err)
119 } else if ev.MagnetURI != "" {
120 _, err := client.AddMagnet(ev.MagnetURI)
122 log.Printf("error adding magnet: %s", err)
125 case dirwatch.Removed:
126 T, ok := client.Torrent(ev.InfoHash)
134 fs := torrentfs.New(client)
135 go exitSignalHandlers(fs)
137 if args.TestPeer != nil {
141 time.Sleep(10 * time.Second)
146 if err := fusefs.Serve(conn, fs); err != nil {
147 return fmt.Errorf("serving fuse fs: %w", err)
150 if err := conn.MountError; err != nil {
151 return fmt.Errorf("mount error: %w", err)