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  }