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"
28 var logger = log.Default.WithNames("main")
31 MetainfoDir string `help:"torrent files in this location describe the contents of the mounted filesystem"`
32 DownloadDir string `help:"location to save torrent data"`
33 MountDir string `help:"location the torrent contents are made available"`
37 ReadaheadBytes tagflag.Bytes
38 ListenAddr *net.TCPAddr
40 MetainfoDir: func() string {
41 _user, err := user.Current()
45 return filepath.Join(_user.HomeDir, ".config/transmission/torrents")
47 ReadaheadBytes: 10 << 20,
48 ListenAddr: &net.TCPAddr{},
51 func exitSignalHandlers(fs *torrentfs.TorrentFS) {
52 c := make(chan os.Signal, 1)
53 signal.Notify(c, syscall.SIGINT, syscall.SIGTERM)
57 err := fuse.Unmount(args.MountDir)
64 func addTestPeer(client *torrent.Client) {
65 for _, t := range client.Torrents() {
66 t.AddPeers([]torrent.PeerInfo{{
76 logger.Levelf(log.Error, "error in main: %v", err)
81 func mainErr() error {
83 if args.MountDir == "" {
84 os.Stderr.WriteString("y u no specify mountpoint?\n")
87 conn, err := fuse.Mount(args.MountDir, fuse.ReadOnly())
89 return fmt.Errorf("mounting: %w", err)
91 defer fuse.Unmount(args.MountDir)
92 // TODO: Think about the ramifications of exiting not due to a signal.
94 cfg := torrent.NewDefaultClientConfig()
95 cfg.DataDir = args.DownloadDir
96 cfg.DisableTrackers = args.DisableTrackers
97 cfg.NoUpload = true // Ensure that downloads are responsive.
98 cfg.SetListenAddr(args.ListenAddr.String())
99 client, err := torrent.NewClient(cfg)
101 return fmt.Errorf("creating torrent client: %w", err)
103 // This is naturally exported via GOPPROF=http.
104 http.DefaultServeMux.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) {
105 client.WriteStatus(w)
107 dw, err := dirwatch.New(args.MetainfoDir)
109 return fmt.Errorf("watching torrent dir: %w", err)
111 dw.Logger = dw.Logger.FilterLevel(log.Info)
113 for ev := range dw.Events {
116 if ev.TorrentFilePath != "" {
117 _, err := client.AddTorrentFromFile(ev.TorrentFilePath)
119 log.Printf("error adding torrent from file %q to client: %v", ev.TorrentFilePath, err)
121 } else if ev.MagnetURI != "" {
122 _, err := client.AddMagnet(ev.MagnetURI)
124 log.Printf("error adding magnet: %s", err)
127 case dirwatch.Removed:
128 T, ok := client.Torrent(ev.InfoHash)
136 fs := torrentfs.New(client)
137 go exitSignalHandlers(fs)
139 if args.TestPeer != nil {
143 time.Sleep(10 * time.Second)
148 logger.Levelf(log.Debug, "serving fuse fs")
149 if err := fusefs.Serve(conn, fs); err != nil {
150 return fmt.Errorf("serving fuse fs: %w", err)
152 logger.Levelf(log.Debug, "fuse fs completed successfully. waiting for conn ready")
154 if err := conn.MountError; err != nil {
155 return fmt.Errorf("mount error: %w", err)