1
0
This repository has been archived on 2023-12-27. You can view files and clone it, but cannot push or open issues or pull requests.
beginning-go/main.go

185 lines
6.3 KiB
Go

package main
import (
"flag"
"fmt"
"os"
"github.com/clockworksoul/smudge"
)
const (
// variables declared within "const" are constants in Go. The type is
// determined by the compiler.
// More info: https://blog.golang.org/constants
// https://gobyexample.com/constants
// heartbeatMillis is used to configure how frequently the gossip protocol
// announces that it is still connected. No need to change this value.
heartbeatMillis = 500
)
var (
// variables declared within "var" are mutable in Go. They can be explicitly
// initialized to a value, or if not set explicitly, default to the "empty
// value" for their type.
// More info: https://golang.org/doc/effective_go.html#variables
// otherClient specifies the address of one running instance of the client.
// If omitted, this client will not initiate a connection to any existing
// client (i.e. this is the first client in a cluster)
otherClient string
// listenPort is where this client will listen for other clients connecting.
// Must not be left empty.
listenPort int
// localUsername is the friendly name we will present to other clients
// instead of our address. Must not be left empty.
localUsername string
// localAddress is the NodeAddress which other Clients will use to reach us.
localAddress NodeAddress
unittestMode = false
)
// printDebug outputs a log message with the "DEBUG:" prefix. This function can
// be edited to easily enable and disable debugging logs without removing all
// the log lines in the codebase.
func printDebug(msg string, args ...interface{}) {
if !unittestMode {
printLogs(fmt.Sprintf("DEBUG: "+msg, args...))
}
}
// printInfo outputs a log message with the "INFO:" prefix. This function can
// be edited to easily enable and disable debugging logs without removing all
// the log lines in the codebase.
func printInfo(msg string, args ...interface{}) {
if !unittestMode {
printLogs(fmt.Sprintf("INFO: "+msg, args...))
}
}
// printError outputs a log message with the "ERROR:" prefix. This function can
// be edited to easily enable and disable error logs without removing all
// the log lines in the codebase.
func printError(msg string, args ...interface{}) {
if !unittestMode {
printLogs(fmt.Sprintf("ERROR: "+msg, args...))
}
}
// cacheLocalIP populates the value of the localAddress global variable.
// localAddress is used to determine if a broadcast was directed to us
// specifically, as it is the address which other clients use to communicate
// with us.
func cacheLocalIP() {
// this pattern of returning a result and an error is extremely prevalent in
// Go. Unlike many languages, exceptions (or in Go, Panics) are very rarely
// used. When a function returns an error, it Must be handled and the result
// disregarded.
// More info: https://blog.golang.org/error-handling-and-go
ip, err := smudge.GetLocalIP()
if err != nil {
fmt.Println("Unable to retrieve local IP", err)
os.Exit(1)
}
localIP := ip.String()
// listenPort, defined above, is a pointer to a number. Take a look at the
// return type of https://golang.org/pkg/flag/#Int
// Prepending our use of listenPort with a * will dereference the pointer,
// giving us a normal int.
localAddress = NodeAddress(fmt.Sprintf("%s:%d", localIP, listenPort))
}
// main is the entry point to the application.
func main() {
// Populate the flag variables at the top of this file with input from the
// user. Afterwards, determine if any required values were omitted.
flag.StringVar(&localUsername, "username", "",
"Friendly name for this client")
flag.IntVar(&listenPort, "listenport", 0,
"Port on which client listens for connections to other clients")
flag.StringVar(&otherClient, "client", "",
"Address of an existing client, if empty do not attempt to connect")
flag.Parse()
if listenPort == 0 {
printError("Listen port is required")
flag.Usage()
os.Exit(1)
} else if localUsername == "" {
printError("Username is required")
flag.Usage()
os.Exit(1)
}
// Now the user input is parsed, lets start configuring the gossip
// communication with other clients. These options were all grabbed from the
// example on the project homepage: https://github.com/clockworksoul/smudge#everything-in-one-place
// Set configuration options
smudge.SetListenPort(listenPort)
smudge.SetHeartbeatMillis(heartbeatMillis)
// Add the status listener
clientList := ClientList(make(map[NodeAddress]ChatClient))
smudge.AddStatusListener(clientList)
// Add the broadcast listener
messenger := Messenger{clients: clientList}
smudge.AddBroadcastListener(&messenger)
// Only attempt to connect to another client if the address for one was
// provided. If not, the client will sit and wait until a client connects.
if otherClient != "" {
// Add a new remote node. To join an existing cluster you must
// add at least one of its healthy member nodes.
if node, err := smudge.CreateNodeByAddress(otherClient); err != nil {
printError("Failed to create a new node from addr: ", err)
os.Exit(1)
} else {
_, err = smudge.AddNode(node)
if err != nil {
printError("Failed to add a node to Smudge: ", err)
os.Exit(1)
}
}
}
// The default logs from smudge just print to stdout and look messy in our
// fancy UI.
smudge.SetLogThreshold(smudge.LogOff)
// Start the server!
// We will run the smudge server in a background go routine. This is similar
// to a new thread, however it is scheduled on real OS threads by the Go
// runtime.
//
// For the scope of this class, you can assume this function is
// running in the background. I encourage reading more about these later
// from a resource such as this: https://gobyexample.com/goroutines
printDebug("Starting Smudge...\n")
go smudge.Begin()
cacheLocalIP()
// Start the username watcher!
// Another go routine, both will be scheduled by the runtime and run as
// frequently as possible, depending on the number of threads given to the
// process.
go clientList.FillMissingInfo()
// Start the gui!
// Notice that here we are not starting in a go routine. If we did then this
// thread (the main one) would reach the end of the main function, exit, and
// kill all the other go routines. We will hand-off control of the program
// to the UI which will listen for input from the user from here out.
printDebug("Starting the GUI...\n")
runGUI(clientList)
}