ae691ada79
I've decided instead to release a minimum viable product for v0.7.0 and get some feedback from the community, and most importantly some motivation as well to be able to keep working on magnetico as it currently feels like a Sisyphean where the development seem to never going to end...
158 lines
4.2 KiB
Go
158 lines
4.2 KiB
Go
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"net"
|
|
"os"
|
|
"os/signal"
|
|
"time"
|
|
|
|
"github.com/jessevdk/go-flags"
|
|
"go.uber.org/zap"
|
|
"go.uber.org/zap/zapcore"
|
|
|
|
"magnetico/magneticod/bittorrent"
|
|
"magnetico/magneticod/dht"
|
|
|
|
"magnetico/persistence"
|
|
)
|
|
|
|
type cmdFlags struct {
|
|
DatabaseURL string `long:"database" description:"URL of the database." required:"yeah"`
|
|
|
|
TrawlerMlAddrs []string `long:"trawler-ml-addr" description:"Address(es) to be used by trawling DHT (Mainline) nodes." default:"0.0.0.0:0"`
|
|
TrawlerMlInterval uint `long:"trawler-ml-interval" description:"Trawling interval in integer deciseconds (one tenth of a second)."`
|
|
|
|
Verbose []bool `short:"v" long:"verbose" description:"Increases verbosity."`
|
|
}
|
|
|
|
type opFlags struct {
|
|
DatabaseURL string
|
|
|
|
TrawlerMlAddrs []string
|
|
TrawlerMlInterval time.Duration
|
|
|
|
Verbosity int
|
|
}
|
|
|
|
func main() {
|
|
loggerLevel := zap.NewAtomicLevel()
|
|
// Logging levels: ("debug", "info", "warn", "error", "dpanic", "panic", and "fatal").
|
|
logger := zap.New(zapcore.NewCore(
|
|
zapcore.NewConsoleEncoder(zap.NewDevelopmentEncoderConfig()),
|
|
zapcore.Lock(os.Stderr),
|
|
loggerLevel,
|
|
))
|
|
defer logger.Sync()
|
|
zap.ReplaceGlobals(logger)
|
|
|
|
// opFlags is the "operational flags"
|
|
opFlags, err := parseFlags()
|
|
if err != nil {
|
|
// Do not print any error messages as jessevdk/go-flags already did.
|
|
return
|
|
}
|
|
|
|
zap.L().Info("magneticod v0.7.0 has been started.")
|
|
zap.L().Info("Copyright (C) 2017 Mert Bora ALPER <bora@boramalper.org>.")
|
|
zap.L().Info("Dedicated to Cemile Binay, in whose hands I thrived.")
|
|
|
|
switch opFlags.Verbosity {
|
|
case 0:
|
|
loggerLevel.SetLevel(zap.WarnLevel)
|
|
case 1:
|
|
loggerLevel.SetLevel(zap.InfoLevel)
|
|
default: // Default: i.e. in case of 2 or more.
|
|
// TODO: print the caller (function)'s name and line number!
|
|
loggerLevel.SetLevel(zap.DebugLevel)
|
|
}
|
|
|
|
zap.ReplaceGlobals(logger)
|
|
|
|
// Handle Ctrl-C gracefully.
|
|
interruptChan := make(chan os.Signal)
|
|
signal.Notify(interruptChan, os.Interrupt)
|
|
|
|
database, err := persistence.MakeDatabase(opFlags.DatabaseURL, false, logger)
|
|
if err != nil {
|
|
logger.Sugar().Fatalf("Could not open the database at `%s`: %s", opFlags.DatabaseURL, err.Error())
|
|
}
|
|
|
|
trawlingManager := dht.NewTrawlingManager(opFlags.TrawlerMlAddrs)
|
|
metadataSink := bittorrent.NewMetadataSink(2 * time.Minute)
|
|
|
|
// The Event Loop
|
|
for stopped := false; !stopped; {
|
|
select {
|
|
case result := <-trawlingManager.Output():
|
|
logger.Info("result: ", zap.String("hash", result.InfoHash.String()))
|
|
exists, err := database.DoesTorrentExist(result.InfoHash[:])
|
|
if err != nil {
|
|
zap.L().Fatal("Could not check whether torrent exists!", zap.Error(err))
|
|
} else if !exists {
|
|
metadataSink.Sink(result)
|
|
}
|
|
|
|
case metadata := <-metadataSink.Drain():
|
|
if err := database.AddNewTorrent(metadata.InfoHash, metadata.Name, metadata.Files); err != nil {
|
|
logger.Sugar().Fatalf("Could not add new torrent %x to the database: %s",
|
|
metadata.InfoHash, err.Error())
|
|
}
|
|
logger.Sugar().Infof("D I S C O V E R E D: `%s` %x", metadata.Name, metadata.InfoHash)
|
|
|
|
case <-interruptChan:
|
|
trawlingManager.Terminate()
|
|
stopped = true
|
|
}
|
|
}
|
|
|
|
if err = database.Close(); err != nil {
|
|
zap.L().Error("Could not close database!", zap.Error(err))
|
|
}
|
|
}
|
|
|
|
func parseFlags() (*opFlags, error) {
|
|
opF := new(opFlags)
|
|
cmdF := new(cmdFlags)
|
|
|
|
_, err := flags.Parse(cmdF)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if cmdF.DatabaseURL == "" {
|
|
zap.S().Fatal("database")
|
|
} else {
|
|
opF.DatabaseURL = cmdF.DatabaseURL
|
|
}
|
|
|
|
if err = checkAddrs(cmdF.TrawlerMlAddrs); err != nil {
|
|
zap.S().Fatalf("Of argument (list) `trawler-ml-addr` %s", err.Error())
|
|
} else {
|
|
opF.TrawlerMlAddrs = cmdF.TrawlerMlAddrs
|
|
}
|
|
|
|
// 1 decisecond = 100 milliseconds = 0.1 seconds
|
|
if cmdF.TrawlerMlInterval == 0 {
|
|
opF.TrawlerMlInterval = time.Duration(1) * 100 * time.Millisecond
|
|
} else {
|
|
opF.TrawlerMlInterval = time.Duration(cmdF.TrawlerMlInterval) * 100 * time.Millisecond
|
|
}
|
|
|
|
opF.Verbosity = len(cmdF.Verbose)
|
|
|
|
return opF, nil
|
|
}
|
|
|
|
func checkAddrs(addrs []string) error {
|
|
for i, addr := range addrs {
|
|
// We are using ResolveUDPAddr but it works equally well for checking TCPAddr(esses) as
|
|
// well.
|
|
_, err := net.ResolveUDPAddr("udp", addr)
|
|
if err != nil {
|
|
return fmt.Errorf("with %d(th) address `%s`: %s", i+1, addr, err.Error())
|
|
}
|
|
}
|
|
return nil
|
|
}
|