+++ /dev/null
-// Mounts a FUSE filesystem backed by torrents and magnet links.
-package main
-
-import (
- "fmt"
- "net"
- "net/http"
- _ "net/http/pprof"
- "os"
- "os/signal"
- "os/user"
- "path/filepath"
- "syscall"
- "time"
-
- "github.com/anacrolix/envpprof"
- _ "github.com/anacrolix/envpprof"
- "github.com/anacrolix/fuse"
- fusefs "github.com/anacrolix/fuse/fs"
- "github.com/anacrolix/log"
- "github.com/anacrolix/tagflag"
-
- "github.com/anacrolix/torrent"
- torrentfs "github.com/anacrolix/torrent/fs"
- "github.com/anacrolix/torrent/util/dirwatch"
-)
-
-var args = struct {
- MetainfoDir string `help:"torrent files in this location describe the contents of the mounted filesystem"`
- DownloadDir string `help:"location to save torrent data"`
- MountDir string `help:"location the torrent contents are made available"`
-
- DisableTrackers bool
- TestPeer *net.TCPAddr
- ReadaheadBytes tagflag.Bytes
- ListenAddr *net.TCPAddr
-}{
- MetainfoDir: func() string {
- _user, err := user.Current()
- if err != nil {
- panic(err)
- }
- return filepath.Join(_user.HomeDir, ".config/transmission/torrents")
- }(),
- ReadaheadBytes: 10 << 20,
- ListenAddr: &net.TCPAddr{},
-}
-
-func exitSignalHandlers(fs *torrentfs.TorrentFS) {
- c := make(chan os.Signal, 1)
- signal.Notify(c, syscall.SIGINT, syscall.SIGTERM)
- for {
- <-c
- fs.Destroy()
- err := fuse.Unmount(args.MountDir)
- if err != nil {
- log.Print(err)
- }
- }
-}
-
-func addTestPeer(client *torrent.Client) {
- for _, t := range client.Torrents() {
- t.AddPeers([]torrent.PeerInfo{{
- Addr: args.TestPeer,
- }})
- }
-}
-
-func main() {
- defer envpprof.Stop()
- err := mainErr()
- if err != nil {
- log.Printf("error in main: %v", err)
- os.Exit(1)
- }
-}
-
-func mainErr() error {
- tagflag.Parse(&args)
- if args.MountDir == "" {
- os.Stderr.WriteString("y u no specify mountpoint?\n")
- os.Exit(2)
- }
- conn, err := fuse.Mount(args.MountDir)
- if err != nil {
- return fmt.Errorf("mounting: %w", err)
- }
- defer fuse.Unmount(args.MountDir)
- // TODO: Think about the ramifications of exiting not due to a signal.
- defer conn.Close()
- cfg := torrent.NewDefaultClientConfig()
- cfg.DataDir = args.DownloadDir
- cfg.DisableTrackers = args.DisableTrackers
- cfg.NoUpload = true // Ensure that downloads are responsive.
- cfg.SetListenAddr(args.ListenAddr.String())
- client, err := torrent.NewClient(cfg)
- if err != nil {
- return fmt.Errorf("creating torrent client: %w", err)
- }
- // This is naturally exported via GOPPROF=http.
- http.DefaultServeMux.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) {
- client.WriteStatus(w)
- })
- dw, err := dirwatch.New(args.MetainfoDir)
- if err != nil {
- return fmt.Errorf("watching torrent dir: %w", err)
- }
- dw.Logger = dw.Logger.FilterLevel(log.Info)
- go func() {
- for ev := range dw.Events {
- switch ev.Change {
- case dirwatch.Added:
- if ev.TorrentFilePath != "" {
- _, err := client.AddTorrentFromFile(ev.TorrentFilePath)
- if err != nil {
- log.Printf("error adding torrent from file %q to client: %v", ev.TorrentFilePath, err)
- }
- } else if ev.MagnetURI != "" {
- _, err := client.AddMagnet(ev.MagnetURI)
- if err != nil {
- log.Printf("error adding magnet: %s", err)
- }
- }
- case dirwatch.Removed:
- T, ok := client.Torrent(ev.InfoHash)
- if !ok {
- break
- }
- T.Drop()
- }
- }
- }()
- fs := torrentfs.New(client)
- go exitSignalHandlers(fs)
-
- if args.TestPeer != nil {
- go func() {
- for {
- addTestPeer(client)
- time.Sleep(10 * time.Second)
- }
- }()
- }
-
- if err := fusefs.Serve(conn, fs); err != nil {
- return fmt.Errorf("serving fuse fs: %w", err)
- }
- <-conn.Ready
- if err := conn.MountError; err != nil {
- return fmt.Errorf("mount error: %w", err)
- }
- return nil
-}