3 Commits

Author SHA1 Message Date
oracle ae02c9dfc0 EKHO-13 | justfile
created justfile, added service file
2026-06-20 00:57:46 -04:00
oracle fae2cceae2 WIP adding names 2026-06-19 00:38:57 -04:00
oracle 552f9ac9c2 removed ghost line bug AGAIN and tested with go1.26.2 (arch btw) 2026-04-28 20:14:33 -04:00
9 changed files with 309 additions and 812 deletions
-30
View File
@@ -1,30 +0,0 @@
server_clean:
rm server
client_clean:
rm client
clean: server_clean client_clean
server:
go build server.go
client:
go build client.go
build: server client
ins_server:
go build -o ekho_server server.go
mv ekho_server /usr/local/bin/
cp ekho.service /etc/systemd/system/ekho.service
mkdir -p ~/.config/ekho
ln -s -r ./list.q ~/.config/ekho/list.q
systemctl daemon-reload
systemctl enable ekho
systemctl start ekho
systemctl status ekho
ins_client:
go build -o ekho client.go
mv ekho /usr/local/bin/
+43 -49
View File
@@ -3,86 +3,80 @@
## Why? ## Why?
To test my abilities to develop networked programs, and to enhance my skills in writing such. To test my abilities to develop networked programs, and to enhance my skills in writing such.
## Requirements ## Requirements
### Hardware ### Hardware
- Client machine - Client machine
- Server machine (not required to be distinct from Client) - Server machine (not required to be distinct from Client)
### Software ### Software
- Tested with [golang] v1.26 - [Golang]
- Installation has only been tested with [just] v1.53 - Linux distro (highly recommened)
- Server installation assumes systemd (tested with v260.2) - ```make``` is also used in this guide, but the makefile is incredibly simple, and can be skipped entirely by running ```go build``` instead
### Installation ### Installation
Golang can be installed on Mac with
``` sh
brew install golang
```
Clone the repo Clone the repo
``` sh ``` sh
git clone https://github.com/alexander-the-alright/ekho git clone https://github.com/alexander-the-alright/ekho
cd ekho cd ekho
``` ```
Using your favorite text editor, change the destination IP and Port to whatever works for you. This is defined on line 247 in client.go as 127.0.0.1:1300.
#### Justfile Now the server file needs to be run on the preferred port, as specified in the client file. This is defined on line 257 in server.go.
It is recommended to use ```just``` for installation, as it has more
capabilities than ```make```, however, there is a makefile included.
To install the server with ```just```, run ```sudo just ins_server```.
You will be prompted to enter a new port number to run the server on (
or leave blank to use 1300). The port will be changed in the source
file before compilation.
To install the client with ```just```, run ```sudo just ins_client```.
You will be prompted to enter the IP (or URL) and port of the server
(or leave blank to use localhost:1300). The IP/URL and port will be
changed in the source file before compilation.
#### Makefile
If you are unable or willing to use ```just```, the included makefile
still has most of the functionality, but it does not change any
important parameters. The following changes are advised before
installation.
1. On the client machine, change the IP/URL and port in the client source file. This can be The binary(s) can be obtained using the makefile
done quickly with ```sed -i -e "s/127.0.0.1:1300/YOUR.IP.GOES.HERE:PORT/" client.go``` If the server is being run on a separate machine, only run make with client flag
2. On the server machine, change the port in the server source file. This can be done with a ``` sh
similar command: ```sed -i -e "s/1300/PORT/" server.go```. (note: this make client
should match the port from bullet 1) ```
3. On the server machine, change the user referenced in the systemd service file. This can be If both binaries are being run on the same machine, ```make all``` will suffice. Although, both of these commands can be bypassed with ```go build```, as this is all ```make``` does anyway.
done with another sed command, ```sed -e -i "s/USER/YOUR_USERNAME/" ekho.service``` ``` sh
make all
```
After making these changes on the respective machines, run either #### NOTE
```sudo make ins_client``` or ```sudo make ins_server``` to compile and install the client In previous versions, the suggested method of installation involved placing bash scripts in ```/etc/update-motd.d/```, however, there have been issues getting that installation method working (namely, having MOTD run user-defined commands), and the current quick and extremely dirty suggested installation method is to just append the user's shell config files (```.bashrc```, ```.bash_profile```, ```.zshrc```, etc) with the paths to the executables.
and/or server binaries.
#### After Installation For example, if the server used zsh, the installation could look something like this:
After installation of the server binary, systemd will display the ``` sh
status of the new service. It should be ```enabled``` and echo "~/abs/path/to/server.o &" >> ~/.zshrc
```running```. ```
Similarly for a client using Bash:
``` sh
echo "~/abs/path/to/client.o" >> ~/.bashrc
```
Any help figuring out the issue with MOTD would be appreciated.
Finally done! Finally done!
### Usage ### Usage
The client uses the following flags The client uses the following flags
``` sh ``` sh
-h - display help message -h - display help message
-a <string> - adds required argument <s> as a quote to server -a <s> - adds required argument <s> as a quote to server
-ip <ip:port> - specify alternate server destination
-l - list all quotes -l - list all quotes
-r [i] - remove; may be run with or without argument -r [i] - remove; may be run with or without argument
-e [i] - edit; may be run with or without argument -ip - specify alternate server destination
-c - list color format specifiers with examples -s - specify the size of the receipt buffer
-s <i> - specify the size of the receipt buffer
-v - display version and exit
-vv - display version and about info and exit
``` ```
The server uses the following flags The server uses the following flags
``` sh ``` sh
-h - display help message -h - display help message
-s <i> - specify the size of the receipt buffer -s - specify the size of the receipt buffer
-v - display version and exit
-vv - display version and about info and exit
``` ```
### How It Works
The sequence diagram can be viewed in ```diag``` folder, which shows the sequence of networked messages for each usage of ekho.
The source code is in the same folder, ```ekho-seq.txt```, and can be uploaded to [sequencediagram.org][seqdiag] to rerender the diagram and view any updates.
### To Be Implemented ### To Be Implemented
- Log all uses/transaction data each day - Log all uses/transaction data each day
- Request, send, and receive the logfile - Request, send, and receive the logfile for any day
- User-specific quotes/quotefiles - User-specific quotes/quotefiles
- Installation (client-side, at least) via MOTD
[golang]: <https://go.dev/doc/install> [golang]: <https://go.dev/doc/install>
[just]: <https://github.com/casey/just> [seqdiag]: <sequencediagram.org>
+90 -418
View File
@@ -1,7 +1,7 @@
// ============================================================================= // =============================================================================
// Auth: alex // Auth: alex
// File: client.go // File: client.go
// Revn: 09-16-2026 4.0.0 // Revn: 01-09-2025 4.0
// Func: ask server for a message of the day quote // Func: ask server for a message of the day quote
// //
// TODO: remove ioutil import // TODO: remove ioutil import
@@ -24,53 +24,32 @@
// imported check() from soary // imported check() from soary
// 09-29-2023: commented handle() // 09-29-2023: commented handle()
//*10-02-2023: commented main(), and friends //*10-02-2023: commented main(), and friends
//#10-05-2023: /remove/ works //*10-05-2023: /remove/ works
// removed -d flag // removed -d flag
// commented // commented
// 06-12-2024: began added PWFT to /la/ // 06-12-2024: began added PWFT to /la/
// 06-13-2024: finished adding PWFT to /la/ // 06-13-2024: finished adding PWFT to /la/
// commented PWFT stuff // commented PWFT stuff
// STARTED BYTE-BASED PWFT REWRITE // STARTED BYTE-BASED PWFT REWRITE
//#06-14-2024: finished byte-based PWFT rewrite //*06-14-2024: finished byte-based PWFT rewrite
// commented // commented
//*01-09-2025: basically just copy/pasted from /la/ into /r1/, /r2/ //*01-09-2025: basically just copy/pasted from /la/ into /r1/, /r2/
// 06-21-2026: copied v and vv flags from server
// copied /edit/ switch case from /remove/ in main()
// pulled /sa/ and /qa/ into separate functions
// 09-03-2026: renamed in() -> inputNumber()
// copied in() and removed all logic
// added firstEdit() and secondEdit() and /e1/ and /e2/
// 09-04-2026: rewrote in() with bufio instead of scanln
//#09-06-2026: fixed blank message error in secondEdit()
// comments
// 09-07-2026: added fatih/color import
// rewrote quoteAnswer() to handle coloring escape
// sequences
//*09-12-2026: commented quoteAnswer()
//*09-15-2026: added -c flag and color code example printing
// changed edit command to be add and then remove instead
// of remove and then add, fixing server OOB error when
// editting only quote in list
//#09-15-2026: finished pretty print implementation
// made listAnswer() print list with color print
// //
// ============================================================================= // =============================================================================
package main package main
import ( import (
"bufio" // NewReader, ReadString
"io" // io.EOF "io" // io.EOF
"io/ioutil" // ReadFile "io/ioutil" // ReadFile
"flag" // BoolVar, StringVar, Parse "flag" // BoolVar, StringVar, Parse
"fmt" // Println "fmt" // Println
"math/rand" // NewSource, New, Intn, *rand.Rand "math/rand" // NewSource, New, Intn, *rand.Rand
"net" // ResolveTCPAddr, DialTCP, conn.Write,Read "net" // ResolveTCPAddr, DialTCP, conn.Write,Read
"os" // Exit, Create, Stdin "os" // Exit, Create
"strconv" // Atoi, Itoa "strconv" // Atoi, Itoa
"strings" // Split "strings" // Split
"time" // Now, UnixNano "time" // Now, UnixNano
"github.com/fatih/color" // color.New, Printf, Println
) )
@@ -106,33 +85,16 @@ func check( err error ) {
// get user input // get user input
func in( prompt string ) string { func in( prompt string ) string {
// instantiate reader
reader := bufio.NewReader( os.Stdin )
fmt.Print( prompt ) // print user prompt fmt.Print( prompt ) // print user prompt
// get user input, read until newline var input string // declare var to hold user input
input, _ := reader.ReadString( '\n' ) fmt.Scanln( &input ) // get user input
// strip trailing newline before returning
return input[:len( input ) - 1]
}
// get user input, must be a number
func inputNumber( prompt string ) string {
// get user input
//input := in( prompt )
fmt.Print( prompt )
var input string
fmt.Scanln( &input )
// try to convert to an int, discard results // try to convert to an int, discard results
_, err := strconv.Atoi( input ) _, err := strconv.Atoi( input )
// if there was an error, user didn't input an int // if there was an error, user didn't input an int
if err != nil { if err != nil {
// warn user against this // warn user against this
fmt.Println( "Input must be a number" ) fmt.Println( "Input must be a number" )
// try to get user input again input = in( prompt ) // try to get user input again
//XXX input = in( prompt )
fmt.Println( "err: ", err.Error() )
// this, like handle() calling handle(), seems like a super // this, like handle() calling handle(), seems like a super
// bad idea, stack-wise... // bad idea, stack-wise...
} }
@@ -151,7 +113,7 @@ func seed() *rand.Rand {
// get a random number // get a random number
func draw( qsize int ) string { func draw() string {
rn := seed() // get a random seed rn := seed() // get a random seed
r := rn.Intn( qsize ) // get a random number using seed r := rn.Intn( qsize ) // get a random number using seed
// convert random number to ascii, because converting // convert random number to ascii, because converting
@@ -161,135 +123,51 @@ func draw( qsize int ) string {
} }
// respond to size answer // get server response and print
func sizeAnswer( subcom []string ) string { func handle( conn net.Conn ) {
// subcom should contain only a size
// convert to int, catch error
size, err := strconv.Atoi( subcom[0] )
if err != nil { // if int conversion failed ( for some reason ) // create buffer to hold response
os.Exit( 1 ) // just quit buffer := make( []byte, tsize )
// TODO make exit codes unique, document them
}
// draw a random number, add to quote request //var buffer [32]byte // create buffer to hold response
return "qr@" + draw( size ) // read n bytes from server into buffer ( byte slice )
n, err := conn.Read( buffer[:] )
check( err ) // check for errors
} // split recv'd message over @, separate header from message
msg := strings.Split( string( buffer[:n] ), "@" )
var resp string // declare response variable
// receive quote, print switch msg[0] { // switch/case over the header
func quoteAnswer( subcom []string ) string { // if message is...
case "sa": // /sa/, size answer
// split subcommand over spaces // convert back half to int, catch error
words := strings.Split( subcom[0], " " ) size, nerr := strconv.Atoi( msg[1] )
if nerr == nil { // if there was no error
// default text is white qsize = size // set size global to recv'd size
c := color.New( color.FgWhite )
for _, w := range words {
// if first char is normal color escape character
if string( w[0] ) == "$" {
// see which color is requested
switch string( w[1] ) {
case "1": // 1 -> red
c = color.New( color.FgRed )
case "2": // 2 -> yellow
c = color.New( color.FgYellow )
case "3": // 3 -> green
c = color.New( color.FgGreen )
case "4": // 4 -> blue
c = color.New( color.FgBlue )
case "5": // 5 -> cyan
c = color.New( color.FgCyan )
case "6": // 6 -> magenta
c = color.New( color.FgMagenta )
case "7": // 7 -> black
c = color.New( color.FgBlack )
case "0": // 0 -> white again
c = color.New( color.FgWhite )
default:
// none of these, continue with the last color
}
// color chosen, print string with trailing space, minus
// first two characters
c.Printf( "%s ", w[2:] )
// if first char is bold color escape character
} else if string( w[0] ) == "#" {
// see which color is requested
switch string( w[1] ) {
case "1": // 1 -> red
c = color.New( color.FgRed, color.Bold )
case "2": // 2 -> yellow
c = color.New( color.FgYellow, color.Bold )
case "3": // 3 -> green
c = color.New( color.FgGreen, color.Bold )
case "4": // 4 -> blue
c = color.New( color.FgBlue, color.Bold )
case "5": // 5 -> cyan
c = color.New( color.FgCyan, color.Bold )
case "6": // 6 -> magenta
c = color.New( color.FgMagenta, color.Bold )
case "7": // 7 -> black
c = color.New( color.FgBlack, color.Bold )
case "0": // 0 -> white again
c = color.New( color.FgWhite, color.Bold )
default:
// none of these, continue with the last color
}
// color chosen, print string with trailing space, minus
// first two characters
c.Printf( "%s ", w[2:] )
} else { // TODO add if for if w[0] is "\"
// using the currently selected color, print the next work
// and a trailing space
c.Printf( "%s ", w )
}
}
c.Println() // print final newline
//fmt.Println( subcom[0] ) // whole-ass just print quote
return "te@success" // set transaction end message
}
// add answer
func addAnswer( subcom []string ) string {
var resp string = "te@"
// if server says add succeeded
if subcom[0] == "success" {
fmt.Println( "Add successful" ) // print success
resp = resp + "success" // set transaction end message
// if server says add failed
} else if subcom[0] == "error" {
fmt.Println( "Add failed" ) // print fail
resp = resp + "error" // set transaction end message
// unknown status message
} else { } else {
// print unknown status // if int conversion failed, just quit
fmt.Println( "Unknown status code: " + subcom[0] ) // TODO make more robust
// TODO should this be unknown instead of error? os.Exit( 1 )
resp = resp + "error" // set transaction end message
} }
return resp req := draw() // draw a random number
} resp = "qr@" + req // create quote query with rng
case "ba": // /ba/, bad answer
// TODO create server-side, and handle client-side
// receive answer to list fmt.Println( "error in " + msg[1] + ": " + msg[2] )
func listAnswer( subcom []string ) string { resp = "te@error" // set transaction end message
case "qa": // /qa/, quote answer
var resp string fmt.Println( msg[1] ) // whole-ass just print quote
resp = "te@success" // set transaction end message
if len( subcom ) > 1 { // multistage send condition case "la": // /la/, list answer
if subcom[0] == "X" { // final message if len( msg ) > 2 { // multistage send condition
if msg[1] == "X" { // final message
// TODO update // TODO update
// read entire temp storage file as byte array // read entire temp storage file as byte array
tfile, err := ioutil.ReadFile( "temp.q" ) tfile, err := ioutil.ReadFile( "temp.q" )
check( err ) // check read for errors check( err ) // check read for errors
// cast file to string, concat with recv'd string // cast file to string, concat with recv'd string
tstr := string( tfile ) + subcom[1] tstr := string( tfile ) + msg[2]
// split concat over newlines, separating quotes // split concat over newlines, separating quotes
tlist := strings.Split( tstr, "\n" ) tlist := strings.Split( tstr, "\n" )
// iterate over array, printing each quote // iterate over array, printing each quote
@@ -308,127 +186,57 @@ func listAnswer( subcom []string ) string {
check( err ) // check file open for errors check( err ) // check file open for errors
// write received message to file, keeping track // write received message to file, keeping track
// of bytes written // of bytes written
n, err := f.WriteString( subcom[1] ) n, err = f.WriteString( msg[2] )
check( err ) // check file write for errors check( err ) // check file write for errors
// cast bytes already written field to int // cast bytes already written field to int
b, err := strconv.Atoi( subcom[0] ) b, err := strconv.Atoi( msg[1] )
check( err ) // check cast for errors check( err ) // check cast for errors
// add bytes just written to bytes already written, cast // add bytes just written to bytes already
// back to string, and append to response variable // written, cast back to string, and append to
// response variable
resp = "lr@" + strconv.Itoa( b + n ) resp = "lr@" + strconv.Itoa( b + n )
} }
} else { } else {
// split recv'd message over \n, create quote list // split recv'd message over \n, create quote list
quotes := strings.Split( subcom[0], "\n" ) quotes := strings.Split( msg[1], "\n" )
for p, v := range quotes { // iterate over quotes for p, v := range quotes { // iterate over quotes
// print num of quote, followed by that quote // print num of quote, followed by that quote
fmt.Print( p + 1, "\t" ) fmt.Println( p + 1, "\t", v )
quoteAnswer( []string{ v } )
} }
resp = "te@success" // set transaction end message resp = "te@success" // set transaction end message
} }
return resp case "aa": // /aa/, add answer
} // if server says add succeeded
if msg[1] == "success" {
fmt.Println( "Add successful" ) // print success
// first round edit request resp = "te@success" // set transaction end message
func firstEdit( subcom []string ) string { // if server says add failed
} else if msg[1] == "error" {
var resp string fmt.Println( "Add failed" ) // print fail
resp = "te@error" // set transaction end message
if len( subcom ) > 1 { // multistage send condition // unknown status message
if subcom[0] == "X" { // final message
// TODO update
// read entire temp storage file as byte array
tfile, err := ioutil.ReadFile( "temp.q" )
check( err ) // check read for errors
// cast file to string, concat with recv'd string
tstr := string( tfile ) + subcom[1]
// split concat over newlines, separating quotes
tlist := strings.Split( tstr, "\n" )
// iterate over array, printing each quote
for p, v := range tlist {
fmt.Println( p + 1, "\t", v )
}
// delete temp storage file
err = os.Remove( "temp.q" )
check( err ) // check rm for errors
// ask user which quote to remove
resp = "e2@" + inputNumber( "\n-> " )
} else { // first or intermediate message
// open temp quote storage file, creating if
// necessary, with write-only permissions, in
// append mode, wr u perm, r g/w perm
f, err := os.OpenFile( "temp.q", os.O_WRONLY | os.O_APPEND | os.O_CREATE, 0644 )
check( err ) // check file open for errors
// write received message to file, keeping track
// of bytes written
n, err := f.WriteString( subcom[1] )
check( err ) // check file write for errors
// cast bytes already written field to int
b, err := strconv.Atoi( subcom[0] )
check( err ) // check cast for errors
// add bytes just written to bytes already written, cast
// back to string, and append to response variable
resp = "e1@" + strconv.Itoa( b + n )
}
} else { } else {
// split recv'd message over \n, create quote list // print unknown status
quotes := strings.Split( subcom[0], "\n" ) fmt.Println( "Unknown status code: " + msg[1] )
for p, v := range quotes { // iterate over quotes // TODO should this be unknown instead of error?
// print num of quote, followed by that quote resp = "te@error" // set transaction end message
fmt.Println( p + 1, "\t", v )
} }
// ask user which quote to edit case "r1": // /r1/, remove ( first transaction )
resp = "e2@" + inputNumber( "\n-> " )
}
return resp
}
// second round edit request ( and also third, technically )
func secondEdit( subcom []string ) string {
// success message and edit request pt2 come to the same place
if subcom[0] == "success" {
// print success and end transaction
fmt.Println( "Edit successful" )
return "te@success"
}
// edit command is an add and then a removal
var resp string
resp = "cr@ar@"
fmt.Println( "as is: " + subcom[1] )
fmt.Print( "preview: " ) // show raw of current quote
_ = quoteAnswer( subcom[1:] ) // display quote properly
// server response is user input for new quote
resp = resp + in( "\nupdate -> " )
// and position of old quote to be removed
resp = resp + "@r2@" + subcom[0]
return resp
}
// list of all quotes, pick which to remove
func firstRemove( subcom []string ) string {
// the idea is that if the user doesn't specify which quote to // the idea is that if the user doesn't specify which quote to
// remove, list all quotes and let the user choose the quote // remove, list all quotes and let the user choose the quote
// to remove. There was probably a way to handle this a little // to remove. There was probably a way to handle this a little
// more simply, but this, I feel, has some nuance to it that I // more simply, but this, I feel, has some nuance to it that I
// actually like // actually like
// redo /r1/, list all quotes
var resp string // split recv'd message over newline to create quote list
if len( msg ) > 2 { // multistage send condition
if len( subcom ) > 1 { // multistage send condition if msg[1] == "X" { // final message
if subcom[0] == "X" { // final message
// TODO update // TODO update
// read entire temp storage file as byte array // read entire temp storage file as byte array
tfile, err := ioutil.ReadFile( "temp.q" ) tfile, err := ioutil.ReadFile( "temp.q" )
check( err ) // check read for errors check( err ) // check read for errors
// cast file to string, concat with recv'd string // cast file to string, concat with recv'd string
tstr := string( tfile ) + subcom[1] tstr := string( tfile ) + msg[2]
// split concat over newlines, separating quotes // split concat over newlines, separating quotes
tlist := strings.Split( tstr, "\n" ) tlist := strings.Split( tstr, "\n" )
// iterate over array, printing each quote // iterate over array, printing each quote
@@ -439,7 +247,7 @@ func firstRemove( subcom []string ) string {
err = os.Remove( "temp.q" ) err = os.Remove( "temp.q" )
check( err ) // check rm for errors check( err ) // check rm for errors
// ask user which quote to remove // ask user which quote to remove
resp = "r2@" + inputNumber( "\n-> " ) resp = "r2@" + in( "\n-> " )
} else { // first or intermediate message } else { // first or intermediate message
// open temp quote storage file, creating if // open temp quote storage file, creating if
// necessary, with write-only permissions, in // necessary, with write-only permissions, in
@@ -448,100 +256,55 @@ func firstRemove( subcom []string ) string {
check( err ) // check file open for errors check( err ) // check file open for errors
// write received message to file, keeping track // write received message to file, keeping track
// of bytes written // of bytes written
n, err := f.WriteString( subcom[1] ) n, err = f.WriteString( msg[2] )
check( err ) // check file write for errors check( err ) // check file write for errors
// cast bytes already written field to int // cast bytes already written field to int
b, err := strconv.Atoi( subcom[0] ) b, err := strconv.Atoi( msg[1] )
check( err ) // check cast for errors check( err ) // check cast for errors
// add bytes just written to bytes already written, cast // add bytes just written to bytes already
// back to string, and append to response variable // written, cast back to string, and append to
// response variable
resp = "r1@" + strconv.Itoa( b + n ) resp = "r1@" + strconv.Itoa( b + n )
} }
} else { } else {
// split recv'd message over \n, create quote list // split recv'd message over \n, create quote list
quotes := strings.Split( subcom[0], "\n" ) quotes := strings.Split( msg[1], "\n" )
for p, v := range quotes { // iterate over quotes for p, v := range quotes { // iterate over quotes
// print num of quote, followed by that quote // print num of quote, followed by that quote
fmt.Println( p + 1, "\t", v ) fmt.Println( p + 1, "\t", v )
} }
// ask user which quote to remove // ask user which quote to remove
resp = "r2@" + inputNumber( "\n-> " ) resp = "r2@" + in( "\n-> " )
} }
return resp case "r2": // /r2/, remove ( second transaction )
}
// second remove, determine if success or failure
func secondRemove( subcom []string ) string {
var resp string
// if server says remove succeeded // if server says remove succeeded
if subcom[0] == "success" { if msg[1] == "success" {
fmt.Println( "Remove successful\n" ) // success fmt.Println( "Remove successful\n" ) // success
// list all quotes to validate remove succeeded // list all quotes to validate remove succeeded
// TODO should /aa/ also make /lr/ before /te/? // TODO should /aa/ also make /lr/ before /te/?
resp = "lr@init" // set transaction end message resp = "lr@init" // set transaction end message
} else if subcom[0] == "error" { // if server says remove failed // if server says remove failed
} else if msg[1] == "error" {
fmt.Println( "Remove failed" ) // print failure fmt.Println( "Remove failed" ) // print failure
resp = "te@error" // set transaction end message resp = "te@error" // set transaction end message
} else { // if unknown status message // if unknown status message
} else {
// TODO there's a known issue where if the buffer // TODO there's a known issue where if the buffer
// size ( -s ) is less than 10, on the server side, // size ( -s ) is less than 10, on the server side,
// 'success' will get cut off ( r2@success ) // 'success' will get cut off ( r2@success )
// and /r2/ will end up down here despite having // and /r2/ will end up down here despite having
// actually worked correctly, and will not display the // actually worked correctly, and with not display the
// list, as it should on success // list, as it should on success
// print unknown status // print unknown status
fmt.Println( "Unknown error code: " + subcom[0] ) fmt.Println( "Unknown error code: " + msg[1] )
// TODO should this be unknown instead of error? // TODO should this be unknown instead of error?
resp = "te@error" // set transaction end message resp = "te@error" // set transaction end message
} }
return resp
}
// get server response and print
func handle( conn net.Conn ) {
// create buffer to hold response
buffer := make( []byte, tsize )
//var buffer [32]byte // create buffer to hold response
// read n bytes from server into buffer ( byte slice )
n, err := conn.Read( buffer[:] )
check( err ) // check for errors
// split recv'd message over @, separate header from message
message := strings.Split( string( buffer[:n] ), "@" )
command := message[0]
var resp string // declare response variable
switch command { // switch/case over the header
// if message is...
case "sa": // /sa/, size answer
resp = sizeAnswer( message[1:] )
case "qa": // /qa/, quote answer
resp = quoteAnswer( message[1:] )
case "aa": // /aa/, add answer
resp = addAnswer( message[1:] )
case "la": // /la/, list answer
resp = listAnswer( message[1:] )
case "e1": // /e1/, edit quote ( first transaction )
resp = firstEdit( message[1:] )
case "e2": // /e2/, edit quote ( second transaction )
resp = secondEdit( message[1:] )
case "r1": // /r1/, remove ( first transaction )
resp = firstRemove( message[1:] )
case "r2": // /r2/, remove ( second transaction )
resp = secondRemove( message[1:] )
case "br": // /br/, bad request case "br": // /br/, bad request
// client has asked for something nonsensical // client has asked for something nonsensical
// TODO write out to file // TODO write out to file
fmt.Println( "Client sent a bad request" ) fmt.Println( "Client sent a bad request" )
resp = "te@error" // set transaction end message resp = "te@error" // set transaction end message
case "ba": // /ba/, bad answer
// TODO create server-side, and handle client-side
fmt.Println( "error in " + message[1] + ": " + message[2] )
resp = "te@error" // set transaction end message
default: // something undefined entirely default: // something undefined entirely
// TODO write out to file // TODO write out to file
//fmt.Println( "Message header not recognized" ) //fmt.Println( "Message header not recognized" )
@@ -559,97 +322,26 @@ func handle( conn net.Conn ) {
// globals // globals
//var qsize int // keep track of num of quotes var qsize int // keep track of num of quotes
var conn net.Conn // keep track of connection var conn net.Conn // keep track of connection
// global flag variables // global flag variables
var add bool // add new quotes to quote file ( on server ) var add bool // add new quotes to quote file ( on server )
var remove bool // remove quotes from quote file ( on server ) var remove bool // remove quotes from quote file ( on server )
var list bool // request entire quote file from server var list bool // request entire quote file from server
var edit bool // edit a pre-existing quote from quote file ( on server )
var co bool // list color codes
var dest string // provide a new destination ip and port var dest string // provide a new destination ip and port
var tsize int // size of transmission buffer var tsize int // size of transmission buffer
var VERS string // version
func main() { func main() {
var v bool // version flag
var vv bool // big version flag
// define flags // define flags
flag.BoolVar( &add, "a", false, "add quote to list" ) flag.BoolVar( &add, "a", false, "add quote to list" )
flag.BoolVar( &remove, "r", false, "remove quote from list" ) flag.BoolVar( &remove, "r", false, "remove quote from list" )
flag.BoolVar( &list, "l", false, "print quote list" ) flag.BoolVar( &list, "l", false, "print quote list" )
flag.BoolVar( &edit, "e", false, "edit quote from list" ) flag.StringVar( &dest, "ip", "earth-1.net:1300", "ip:port of server" )
flag.BoolVar( &co, "c", false, "list color codes with examples" )
flag.StringVar( &dest, "ip", "127.0.0.1:1300", "ip:port of server" )
flag.IntVar( &tsize, "s", 256, "transmission size ( bytes )" ) flag.IntVar( &tsize, "s", 256, "transmission size ( bytes )" )
flag.BoolVar( &v, "v", false, "display version and exit" ) flag.Parse() // process flags
flag.BoolVar( &vv, "vv", false, "display version and about info and exit" )
flag.Parse()
// FIXME get better versioning method
VERS = "4.0.0"
if vv { // display info and exit
fmt.Println( "ekho client v" + VERS )
fmt.Println( "ekho is named after Echo the Oread" )
fmt.Println( "in Greek mythology, Echo was cursed by Hera to only be able to say the last words spoken to her" )
fmt.Println( "i found this to be quite fitting for a MOTD server" )
return
} else if v { // display info and exit
fmt.Println( VERS )
return
}
if co { // display color codes and examples and quit
// print color code and color example on same line
// color.XYZ print inserts newline
fmt.Print( "$1: " )
color.Red( "red" )
fmt.Print( "$2: " )
color.Yellow( "yellow" )
fmt.Print( "$3: " )
color.Green( "green" )
fmt.Print( "$4: " )
color.Blue( "blue" )
fmt.Print( "$5: " )
color.Cyan( "cyan" )
fmt.Print( "$6: " )
color.Magenta( "magenta" )
fmt.Print( "$7: " )
color.Black( "black" )
fmt.Print( "$0: " )
color.White( "white (reset)" )
// print bold codes
fmt.Print( "\n#1: " )
// create bold color object, change out actual color and print
bold := color.New( color.Bold, color.FgRed )
bold.Println( "bold red" )
fmt.Print( "#2: " )
bold.Add( color.FgYellow )
bold.Println( "bold yellow" )
fmt.Print( "#3: " )
bold.Add( color.FgGreen )
bold.Println( "bold green" )
fmt.Print( "#4: " )
bold.Add( color.FgBlue )
bold.Println( "bold blue" )
fmt.Print( "#5: " )
bold.Add( color.FgCyan )
bold.Println( "bold Cyan" )
fmt.Print( "#6: " )
bold.Add( color.FgMagenta )
bold.Println( "bold magenta" )
fmt.Print( "#7: " )
bold.Add( color.FgBlack )
bold.Println( "bold black" )
fmt.Print( "#0: " )
bold.Add( color.FgWhite )
bold.Println( "bold white" )
return // bail
}
service := dest // declare server ip:port service := dest // declare server ip:port
@@ -683,27 +375,6 @@ func main() {
fmt.Println( "/a/ flag must be used with an arugment" ) fmt.Println( "/a/ flag must be used with an arugment" )
os.Exit( 1 ) os.Exit( 1 )
} }
case edit: // if edit flag is active
// if arguments were specified
if len( flag.Args() ) > 0 {
// argument is supposed to the number of the quote to
// be edited, get first arg
// cast to int just to make sure it's a valid number
// assuming the user knows what they're doing
_, rerr := strconv.Atoi( flag.Args()[0] )
// if there was an error, just pretend there's no args
if rerr != nil {
command = "e1@init" // remove request 1
} else {
// if user knows the number, take care of it
// arg[0] is a string, so concat is cool
// jump right into edit request 2
command = "e2@" + flag.Args()[0]
}
} else { // if no argument was specified for edit
// jump right into edit request 1
command = "e1@init"
}
case remove: // if remove flag is active case remove: // if remove flag is active
// if arguments were specified // if arguments were specified
if len( flag.Args() ) > 0 { if len( flag.Args() ) > 0 {
@@ -715,6 +386,7 @@ func main() {
// if there was an error, just pretend there's no args // if there was an error, just pretend there's no args
if rerr != nil { if rerr != nil {
command = "r1@init" // remove request 1 command = "r1@init" // remove request 1
// TODO come up w/ useful argument, replace dumvar
} else { } else {
// if user knows the number, take care of it // if user knows the number, take care of it
// arg[0] is a string, so concat is cool // arg[0] is a string, so concat is cool
+3 -3
View File
@@ -6,9 +6,9 @@ After=network.target
Type=simple Type=simple
Restart=always Restart=always
RestartSec=5 RestartSec=5
User=USER User=
ExecStart=/usr/local/bin/ekho_server ExecStart=/usr/local/bin/server
WorkingDirectory=/home/USER/.config/ekho WorkingDirectory=/home/
[Install] [Install]
WantedBy=multi-user.target WantedBy=multi-user.target
-11
View File
@@ -1,11 +0,0 @@
module earth-1.net/oracle/ekho
go 1.27.1
require github.com/fatih/color v1.19.0
require (
github.com/mattn/go-colorable v0.1.14 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
golang.org/x/sys v0.42.0 // indirect
)
-9
View File
@@ -1,9 +0,0 @@
github.com/fatih/color v1.19.0 h1:Zp3PiM21/9Ld6FzSKyL5c/BULoe/ONr9KlbYVOfG8+w=
github.com/fatih/color v1.19.0/go.mod h1:zNk67I0ZUT1bEGsSGyCZYZNrHuTkJJB+r6Q9VuMi0LE=
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo=
golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
+11 -75
View File
@@ -33,90 +33,26 @@ ins_server:
if [[ $PORT -ne "" ]]; then if [[ $PORT -ne "" ]]; then
sed -i -e "s/1300/$PORT/" server.go sed -i -e "s/1300/$PORT/" server.go
fi fi
echo "go build -o ekho_server server.go" echo "go build server.go"
go build -o ekho_server server.go go build server.go
echo "mv ekho_server /usr/local/bin/"
mv ekho_server /usr/local/bin/
USER=($(users)) USER=($(users))
sed -i -e "s/USER/${USER[0]}/" ekho.service sed -i -e "s/^User=/User=${USER[0]}/" ekho.service
echo "cp ekho.service /etc/systemd/system/" sed -i -e "s/^WorkingDirectory=.*/WorkingDirectory=\/home\/${USER[0]}\//" ekho.service
cp ekho.service /etc/systemd/system/ echo "sudo mv server /usr/local/bin/server"
mkdir -p ~/.config/ekho sudo mv server /usr/local/bin/server
echo "ln -s -r ./list.q ~/.config/ekho/list.q" echo "sudo mv ekho.service /etc/systemd/system/"
ln -s -r ./list.q ~/.config/ekho/list.q sudo mv ekho.service /etc/systemd/system/
echo "systemctl daemon-reload"
systemctl daemon-reload
echo "systemctl enable ekho"
systemctl enable ekho
echo "systemctl start ekho"
systemctl start ekho
echo "systemctl status ekho"
systemctl status ekho
# install client # install client
ins_client: ins_client:
#!/usr/bin/bash #!/usr/bin/bash
echo "enter url or ip and port of server, 'aaa.bbb.ccc.ddd:port" echo "enter ip and port of server, 'aaa.bbb.ccc.ddd:port"
read IP_PORT read IP_PORT
sed -i -e "s/:1300/$IP_PORT/" client.go sed -i -e "s/:1300/$IP_PORT/" client.go
echo "go build -o ekho client.go" echo "go build -o ekho client.go"
go build -o ekho client.go go build -o ekho client.go
sed -i -e "s/$IP_PORT/:1300/" client.go sed -i -e "s/$IP_PORT/:1300/" client.go
echo "mv ekho /usr/local/bin/" echo "sudo mv ekho /usr/local/bin/"
mv ekho /usr/local/bin/ sudo mv ekho /usr/local/bin/
echo "add 'ekho' to shell config (config.fish, .bashrc, etc)" echo "add 'ekho' to shell config (config.fish, .bashrc, etc)"
# test server
stest:
@echo "creating server test session"
@tmux new-session -Ad -s "server test"
@tmux split-window -h -t "server test:1" -l 10%
@tmux split-window -v -t "server test:1.2"
@tmux send-keys -t "server test:1.1" "clear" Enter
@tmux send-keys -t "server test:1.2" "clear" Enter
@tmux send-keys -t "server test:1.3" "clear" Enter
@tmux rename-window "server test"
@tmux send-keys -t "server test:1.1" "vim server.go" Enter
@tmux send-keys -t "server test:1.2" "./server"
@tmux send-keys -t "server test:1.3" "./client"
@tmux attach -t "server test"
# test client
ctest:
@echo "creating client test session"
@tmux new-session -Ad -s "client test"
@tmux split-window -h -t "client test:1" -l 10%
@tmux split-window -v -t "client test:1.2"
@tmux send-keys -t "client test:1.1" "clear" Enter
@tmux send-keys -t "client test:1.2" "clear" Enter
@tmux send-keys -t "client test:1.3" "clear" Enter
@tmux rename-window "client test"
@tmux send-keys -t "client test:1.1" "vim client.go" Enter
@tmux send-keys -t "client test:1.2" "./server" Enter
@tmux send-keys -t "client test:1.3" "./client"
@tmux attach -t "client test"
# dev work
dev:
#!/usr/bin/bash
REPO=$(basename $(git rev-parse --show-toplevel))
BRANCH=$(git branch --show-current)
tmux has-session -t "$REPO->$BRANCH"
if [[ $? -eq 0 ]]; then
tmux attach-session -t "$REPO->$BRANCH"
else
tmux new-session -Ad -s "$REPO->$BRANCH"
tmux rename-window "write"
tmux new-window -a -t 1 -n "test"
tmux new-window -a -t 2 -n "git" lazygit
tmux new-window -a -t 3 -n "man"
tmux select-window -t 2
tmux select-window -t 1
tmux attach -t "$REPO->$BRANCH"
fi
# prep repo to be committed
prep:
mv justfile test_justfile
mv prod_justfile justfile
+9 -1
View File
@@ -1 +1,9 @@
thank you for using $1ekho @user:
shmuley
floglin
@all:
blingo
no but like actually blingo shmingo
i would prefer not to
@default:
thank you for using ekho
+54 -117
View File
@@ -1,13 +1,15 @@
// ============================================================================= // =============================================================================
// Auth: alex // Auth: alex
// File: server.go // File: server.go
// Revn: 09-15-2026 3.0.2 // Revn: 05-12-2026 2.1.2
// Func: host MOTD connection // Func: host MOTD connection
// //
// TODO: add user-specific prints // TODO:
// add user-specific prints
// write usage and errors to logfile // write usage and errors to logfile
// remove ioutil import // remove ioutil import
// send logfile to client on request // send logfile to client on request
// add color printing to qfile
// add flags? // add flags?
// add more /codes/ to handle // add more /codes/ to handle
// ============================================================================= // =============================================================================
@@ -49,11 +51,6 @@
// line bug in /la/ AGAIN // line bug in /la/ AGAIN
// 05-11-2026: updated fread() to handle users in qfile // 05-11-2026: updated fread() to handle users in qfile
// 05-12-2026: added v and vv input flags // 05-12-2026: added v and vv input flags
// 09-05-2026: buncha shit happened, dropped the commit
//#09-06-2026: added /e1/, /e2/, and /cr/, got edit working
// 09-12-2026: minor comments in receive()
//*09-15-2026: fixed OOB error in /qa/ for perhaps the last time
// fixed bad Atoi conversion bug for perhaps the last time
// //
// ============================================================================= // =============================================================================
@@ -124,9 +121,14 @@ func fread() string {
// bugs where a blank item was introduced into qlist and calling // bugs where a blank item was introduced into qlist and calling
// /la/ would produce a blank line on the client side that didn't // /la/ would produce a blank line on the client side that didn't
// really exist // really exist
qfile = qfile[:len( qfile )] qfile = qfile[:len( qfile ) - 1]
// cast bytes to string, split string over newline into array // cast bytes to string, split string over newline into array
qlist = strings.Split( string( qfile ), "\n" ) //qlist = strings.Split( string( qfile ), "\n" )
profiles := strings.Split( string( qfile ), "@" )
for _, p := range profiles {
quotes := strings.Split( p, ":" )
qlist[quotes[0]] = strings.Split( quotes[1], "\n" )
}
return string( qfile ) // return file as string return string( qfile ) // return file as string
} }
@@ -143,8 +145,8 @@ func writeFile( f string ) {
} }
// receive from connection, send to get processed, send out response // handle connections ( new and improved )
func receive( conn net.Conn ) { func handle( conn net.Conn ) {
// create buffer to hold read message // create buffer to hold read message
buffer := make( []byte, tsize ) buffer := make( []byte, tsize )
@@ -153,29 +155,20 @@ func receive( conn net.Conn ) {
n, err := conn.Read( buffer[:] ) n, err := conn.Read( buffer[:] )
checkN( err, conn ) // make sure read worked checkN( err, conn ) // make sure read worked
// message anatomy
// command@number[@user]
// @ @ delimiter
// command command, like list request or quote request
// number sequence or quote number
// user quote username if applicable, default if not give
// add request message anatomy
// command@quote[@user]
// quote quote to be added
// user quote username, default if not given
// cast message to string, take first n bytes, split over @ // cast message to string, take first n bytes, split over @
command := strings.Split( string( buffer[:n] ), "@" ) command := strings.Split( string( buffer[:n] ), "@" )
// if command is to end transaction
if command[0] == "te" {
conn.Close() // end transaction
return // and bail
}
// process command get response
resp := handle( command )
// write response to client
_, err = conn.Write( []byte( resp ) )
checkN( err, conn ) // make sure write worked
receive( conn ) // call handle() again to get response from client
}
// handle connections ( new and improved )
func handle( command []string ) string {
// declare response string // declare response string
var resp string var resp string
switch command[0] { // switch/case over opcode switch command[0] { // switch/case over opcode
@@ -186,9 +179,20 @@ func handle( command []string ) string {
// concat and send // concat and send
resp = "sa@" + num // size answer resp = "sa@" + num // size answer
case "qr": // /qr/, quote request case "qr": // /qr/, quote request
// see if user was sent
subcommands := strings.Split( command[1], ":" )
// keep track of name, or default if need be
var name string
if len( subcommands ) == 1 {
name = "default"
} else {
name = subcommands[1]
}
// convert message argument to string to make sure it's a // convert message argument to string to make sure it's a
// valid number // valid number
num, nerr := strconv.Atoi( command[1] ) num, nerr := strconv.Atoi( subcommands[0] )
// nerr will not be nil if comm[1] is not a number // nerr will not be nil if comm[1] is not a number
if nerr != nil { if nerr != nil {
// formulate an error message // formulate an error message
@@ -196,7 +200,7 @@ func handle( command []string ) string {
resp = "ba@qr@!" // bad answer resp = "ba@qr@!" // bad answer
} else { // comm[1] was a number, but... } else { // comm[1] was a number, but...
switch { // how do we know it's a valid number switch { // how do we know it's a valid number
case num > len( qlist ): case num > len( qlist[name] ):
// bad answer in quote request // bad answer in quote request
resp = "ba@qr@>" resp = "ba@qr@>"
// why bad? num is > // why bad? num is >
@@ -206,7 +210,7 @@ func handle( command []string ) string {
// why bad? num is - // why bad? num is -
default: default:
// good num, get quote, send off // good num, get quote, send off
resp = "qa@" + qlist[num] // quote answer resp = "qa@" + qlist[name][num] // quote answer
} }
} }
case "lr": // /lr/, list request case "lr": // /lr/, list request
@@ -248,83 +252,11 @@ func handle( command []string ) string {
} }
case "ar": // /ar/, add request case "ar": // /ar/, add request
// append new quote to list // append new quote to list
qlist = append( qlist, command[1] ) qlist["f"] = append( qlist["f"], command[1] )
// also append new quote to file variable // also append new quote to file variable
qfile = qfile + "\n" + command[1] qfile = qfile + "\n" + command[1]
writeFile( qfile ) writeFile( qfile )
resp = "aa@success" // add answer resp = "aa@success" // add answer
case "cr":
if len( command ) != 5 {
return "ba@bad request"
}
//mes1 := strings.Join( command[1:3], "@" )
//mes1 := strings.Join( command[3:], "@" )
_ = handle( command[1:3] )
_ = handle( command[3:] )
resp = "e2@success"
case "e1":
resp = "e1@" // begin crafting response
// file too big for single transmission
// initial header size is 5, e1@1@, so counting that into
// the transmission size, the file must be longer than 251
// ( default ) bytes to reach this block of code
if len( qfile ) > ( tsize - 5 ) {
if command[1] == "init" { // first request
// add message number, delim, and the first
// transmittable bytes
resp = resp + "0@" + qfile[:( tsize - 5 )]
} else { // subsequent requests
// convert byte count to int
bytes, err := strconv.Atoi( command[1] )
check( err ) // make sure convert worked
// replace tx'd bytes and delim to new header
resp = resp + command[1] + "@"
rlen := len( resp ) // capture header length
// slices are always of size "txsize - header
// length", so find end byte
end := bytes + ( tsize - rlen )
// if end goes beyond the bounds of the list
if end > len( qfile ) {
// replace byte count with X to signal
// final message, concat the rest of the file,
// beginning with the start index
resp = "e1@X@" + qfile[bytes:]
} else {
// get slice from start index to end, concat
// with response
resp = resp + qfile[bytes:end]
}
}
} else { // file is smaller than the tx size
// just concat entire list to send
resp = resp + qfile // list answer
}
case "e2":
// convert message argument to string to make sure it's a
// valid number
num, nerr := strconv.Atoi( command[1] )
// nerr will not be nil if comm[1] is not a number
if nerr != nil {
// formulate an error message
// TODO flesh this out more
resp = "ba@qr@!" // bad answer
} else { // comm[1] was a number, but...
switch { // how do we know it's a valid number
case num > len( qlist ):
// bad answer in quote request
resp = "ba@qr@>"
// why bad? num is >
case num < 0:
// bad answer in quote request
resp = "ba@qr@-"
// why bad? num is -
default:
// good num, get quote, send off
resp = "e2@" + command[1] + "@" + qlist[num - 1]
}
}
case "r1": // /r1/, remove request 1 case "r1": // /r1/, remove request 1
resp = "r1@" // begin crafting response resp = "r1@" // begin crafting response
// file too big for single transmission // file too big for single transmission
@@ -387,14 +319,14 @@ func handle( command []string ) string {
default: default:
// replace the quote at that position with // replace the quote at that position with
// final quote // final quote
qlist[num] = qlist[len( qlist ) - 1] qlist["f"][num] = qlist["f"][len( qlist ) - 1]
// remove second copy of quote // remove second copy of quote
qlist = qlist[:len( qlist ) - 1] qlist["f"] = qlist["f"][:len( qlist ) - 1]
// O(1) remove from list at any position // O(1) remove from list at any position
// must entirely recreate quote string // must entirely recreate quote string
var f string = "" // init empty string var f string = "" // init empty string
// iterate over list, disregard index // iterate over list, disregard index
for _, v := range qlist { for _, v := range qlist["f"] {
// add next quote to file and separate // add next quote to file and separate
// quotes with newline // quotes with newline
f = f + v + "\n" f = f + v + "\n"
@@ -417,20 +349,25 @@ func handle( command []string ) string {
case "te": // /te/, transaction end case "te": // /te/, transaction end
// TODO should I be sending something? // TODO should I be sending something?
// TODO is it better to close now or outside of handle() // TODO is it better to close now or outside of handle()
//conn.Close() // close connection and leave conn.Close() // close connection and leave
return "" // return from handle(), finish connection return // return from handle(), finish connection
default: // opcode not recognized default: // opcode not recognized
// TODO there should be more being down, here, right? // TODO there should be more being down, here, right?
return "" // return from handle(), finish connection return // return from handle(), finish connection
} }
return resp // write response to client
_, err = conn.Write( []byte( resp ) )
checkN( err, conn ) // make sure write worked
handle( conn ) // call handle() again to get response from client
} }
// globals // globals
var qlist []string // list of quotes //var qlist []string // list of quotes
var qlist = make( map[string][]string )
var qfile string // file object var qfile string // file object
var tsize int // size of transmission buffer var tsize int // size of transmission buffer
var VERS string // version var VERS string // version
@@ -454,7 +391,7 @@ func main() {
flag.Parse() flag.Parse()
// FIXME get better versioning method // FIXME get better versioning method
VERS = "3.0.2" VERS = "2.1.2"
if vv { // display info and exit if vv { // display info and exit
fmt.Println( "ekho server v" + VERS ) fmt.Println( "ekho server v" + VERS )
@@ -490,7 +427,7 @@ func main() {
// wait, create connection when found // wait, create connection when found
conn, err := listener.Accept() conn, err := listener.Accept()
check( err ) // make sure connection works check( err ) // make sure connection works
go receive( conn ) // handle connection go handle( conn ) // handle connection
} }
} }