github.com/attic-labs/noms@v0.0.0-20210827224422-e5fa29d95e8b/samples/go/decent/ipfs-chat/main.go (about)

     1  // See: https://github.com/attic-labs/noms/issues/3808
     2  // +build ignore
     3  
     4  // Copyright 2017 Attic Labs, Inc. All rights reserved.
     5  // Licensed under the Apache License, version 2.0:
     6  // http://www.apache.org/licenses/LICENSE-2.0
     7  
     8  package main
     9  
    10  import (
    11  	"fmt"
    12  	"log"
    13  	"os"
    14  	"os/signal"
    15  	"runtime"
    16  	"syscall"
    17  
    18  	"github.com/attic-labs/noms/go/chunks"
    19  	"github.com/attic-labs/noms/go/d"
    20  	"github.com/attic-labs/noms/go/datas"
    21  	"github.com/attic-labs/noms/go/ipfs"
    22  	"github.com/attic-labs/noms/go/spec"
    23  	"github.com/attic-labs/noms/samples/go/decent/dbg"
    24  	"github.com/attic-labs/noms/samples/go/decent/lib"
    25  	"github.com/ipfs/go-ipfs/core"
    26  	"github.com/jroimartin/gocui"
    27  	kingpin "gopkg.in/alecthomas/kingpin.v2"
    28  )
    29  
    30  func main() {
    31  	// allow short (-h) help
    32  	kingpin.CommandLine.HelpFlag.Short('h')
    33  
    34  	clientCmd := kingpin.Command("client", "runs the ipfs-chat client UI")
    35  	clientTopic := clientCmd.Flag("topic", "IPFS pubsub topic to publish and subscribe to").Default("ipfs-chat").String()
    36  	username := clientCmd.Flag("username", "username to sign in as").String()
    37  	nodeIdx := clientCmd.Flag("node-idx", "a single digit to be used as last digit in all port values: api, gateway and swarm (must be 0-9 inclusive)").Default("-1").Int()
    38  	clientDS := clientCmd.Arg("dataset", "the dataset spec to store chat data in").Required().String()
    39  
    40  	importCmd := kingpin.Command("import", "imports data into a chat")
    41  	importDir := importCmd.Flag("dir", "directory that contains data to import").Default("./data").ExistingDir()
    42  	importDS := importCmd.Arg("dataset", "the dataset spec to import chat data to").Required().String()
    43  
    44  	daemonCmd := kingpin.Command("daemon", "runs a daemon that simulates filecoin, eagerly storing all chunks for a chat")
    45  	daemonTopic := daemonCmd.Flag("topic", "IPFS pubsub topic to publish and subscribe to").Default("ipfs-chat").String()
    46  	daemonInterval := daemonCmd.Flag("interval", "amount of time to wait before publishing state to network").Default("5s").Duration()
    47  	daemonNodeIdx := daemonCmd.Flag("node-idx", "a single digit to be used as last digit in all port values: api, gateway and swarm (must be 0-9 inclusive)").Default("-1").Int()
    48  	daemonDS := daemonCmd.Arg("dataset", "the dataset spec indicating ipfs repo to use").Required().String()
    49  
    50  	kingpin.CommandLine.Help = "A demonstration of using Noms to build a scalable multiuser collaborative application."
    51  
    52  	expandRLimit()
    53  	switch kingpin.Parse() {
    54  	case "client":
    55  		cInfo := lib.ClientInfo{
    56  			Topic:    *clientTopic,
    57  			Username: *username,
    58  			Idx:      *nodeIdx,
    59  			IsDaemon: false,
    60  			Delegate: lib.IPFSEventDelegate{},
    61  		}
    62  		runClient(*clientDS, cInfo)
    63  	case "import":
    64  		lib.RunImport(*importDir, *importDS)
    65  	case "daemon":
    66  		cInfo := lib.ClientInfo{
    67  			Topic:    *daemonTopic,
    68  			Username: "daemon",
    69  			Interval: *daemonInterval,
    70  			Idx:      *daemonNodeIdx,
    71  			IsDaemon: true,
    72  			Delegate: lib.IPFSEventDelegate{},
    73  		}
    74  		runDaemon(*daemonDS, cInfo)
    75  	}
    76  }
    77  
    78  func runClient(ipfsSpec string, cInfo lib.ClientInfo) {
    79  	dbg.SetLogger(lib.NewLogger(cInfo.Username))
    80  
    81  	sp, err := spec.ForDataset(ipfsSpec)
    82  	d.CheckErrorNoUsage(err)
    83  
    84  	if !isIPFS(sp.Protocol) {
    85  		fmt.Println("ipfs-chat requires an 'ipfs' dataset")
    86  		os.Exit(1)
    87  	}
    88  
    89  	node, cs := initIPFSChunkStore(sp, cInfo.Idx)
    90  	db := datas.NewDatabase(cs)
    91  
    92  	// Get the head of specified dataset.
    93  	ds := db.GetDataset(sp.Path.Dataset)
    94  	ds, err = lib.InitDatabase(ds)
    95  	d.PanicIfError(err)
    96  
    97  	events := make(chan lib.ChatEvent, 1024)
    98  	t := lib.CreateTermUI(events)
    99  	defer t.Close()
   100  
   101  	d.PanicIfError(t.Layout())
   102  	t.ResetAuthors(ds)
   103  	t.UpdateMessages(ds, nil, nil)
   104  
   105  	go lib.ProcessChatEvents(node, ds, events, t, cInfo)
   106  	go lib.ReceiveMessages(node, events, cInfo)
   107  
   108  	if err := t.Gui.MainLoop(); err != nil && err != gocui.ErrQuit {
   109  		dbg.Debug("mainloop has exited, err:", err)
   110  		log.Panicln(err)
   111  	}
   112  }
   113  
   114  func runDaemon(ipfsSpec string, cInfo lib.ClientInfo) {
   115  	dbg.SetLogger(log.New(os.Stdout, "", 0))
   116  
   117  	sp, err := spec.ForDataset(ipfsSpec)
   118  	d.CheckErrorNoUsage(err)
   119  
   120  	if !isIPFS(sp.Protocol) {
   121  		fmt.Println("ipfs-chat requires an 'ipfs' dataset")
   122  		os.Exit(1)
   123  	}
   124  
   125  	// Create/Open a new network chunkstore
   126  	node, cs := initIPFSChunkStore(sp, cInfo.Idx)
   127  	db := datas.NewDatabase(cs)
   128  
   129  	// Get the head of specified dataset.
   130  	ds := db.GetDataset(sp.Path.Dataset)
   131  	ds, err = lib.InitDatabase(ds)
   132  	d.PanicIfError(err)
   133  
   134  	events := make(chan lib.ChatEvent, 1024)
   135  	handleSIGQUIT(events)
   136  
   137  	go lib.ReceiveMessages(node, events, cInfo)
   138  	lib.ProcessChatEvents(node, ds, events, nil, cInfo)
   139  }
   140  
   141  func handleSIGQUIT(events chan<- lib.ChatEvent) {
   142  	sigChan := make(chan os.Signal)
   143  	go func() {
   144  		for range sigChan {
   145  			stacktrace := make([]byte, 1024*1024)
   146  			length := runtime.Stack(stacktrace, true)
   147  			dbg.Debug(string(stacktrace[:length]))
   148  			events <- lib.ChatEvent{EventType: lib.QuitEvent}
   149  		}
   150  	}()
   151  	signal.Notify(sigChan, os.Interrupt)
   152  	signal.Notify(sigChan, syscall.SIGQUIT)
   153  }
   154  
   155  // IPFS can use a lot of file decriptors. There are several bugs in the IPFS
   156  // repo about this and plans to improve. For the time being, we bump the limits
   157  // for this process.
   158  func expandRLimit() {
   159  	var rLimit syscall.Rlimit
   160  	err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, &rLimit)
   161  	d.Chk.NoError(err, "Unable to query file rlimit: %s", err)
   162  	if rLimit.Cur < rLimit.Max {
   163  		rLimit.Max = 64000
   164  		rLimit.Cur = 64000
   165  		err = syscall.Setrlimit(syscall.RLIMIT_NOFILE, &rLimit)
   166  		d.Chk.NoError(err, "Unable to increase number of open files limit: %s", err)
   167  	}
   168  	err = syscall.Getrlimit(syscall.RLIMIT_NOFILE, &rLimit)
   169  	d.Chk.NoError(err)
   170  
   171  	err = syscall.Getrlimit(8, &rLimit)
   172  	d.Chk.NoError(err, "Unable to query thread rlimit: %s", err)
   173  	if rLimit.Cur < rLimit.Max {
   174  		rLimit.Max = 64000
   175  		rLimit.Cur = 64000
   176  		err = syscall.Setrlimit(8, &rLimit)
   177  		d.Chk.NoError(err, "Unable to increase number of threads limit: %s", err)
   178  	}
   179  	err = syscall.Getrlimit(8, &rLimit)
   180  	d.Chk.NoError(err)
   181  }
   182  
   183  func initIPFSChunkStore(sp spec.Spec, nodeIdx int) (*core.IpfsNode, chunks.ChunkStore) {
   184  	// recreate database so that we can have control of chunkstore's ipfs node
   185  	node := ipfs.OpenIPFSRepo(sp.DatabaseName, nodeIdx)
   186  	cs := ipfs.ChunkStoreFromIPFSNode(sp.DatabaseName, sp.Protocol == "ipfs-local", node, 1)
   187  	return node, cs
   188  }
   189  
   190  func isIPFS(protocol string) bool {
   191  	return protocol == "ipfs" || protocol == "ipfs-local"
   192  }