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 }