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