func display(displayChan chan displayInfoStruct) {
	var lastDisplayLength int
	var currentDisplay displayInfoStruct
	var displayQueue []displayInfoStruct
	for displayInfo := range displayChan {
		if currentDisplay.fqTable == "" {
			currentDisplay = displayInfo
		}
		if getDisplayTable() == "" && currentDisplay.status == "Downloading" {
			setDisplayTable(currentDisplay.fqTable)
		}
		if currentDisplay.fqTable == displayInfo.fqTable {
			fmt.Fprintf(displayInfo.w, strings.Repeat(" ", lastDisplayLength)+"\r")
			line := fmt.Sprintf("%s: %s", displayInfo.status, displayInfo.fqTable)
			lastDisplayLength = len(line)
			fmt.Fprintf(displayInfo.w, line+"\r")
			if displayInfo.status == "Restored" || displayInfo.status == "ERROR" {
				fmt.Fprintf(displayInfo.w, "\n")
				if len(displayQueue) == 0 {
					currentDisplay.fqTable = ""
				} else {
					var tmpQueue []displayInfoStruct
					for i := 0; i < len(displayQueue); i++ {
						if displayQueue[i].status == "Restored" || displayQueue[i].status == "ERROR" {
							line := fmt.Sprintf("%s: %s", displayQueue[i].status, displayQueue[i].fqTable)
							fmt.Fprintf(displayInfo.w, line+"\n")
						} else if displayQueue[i].fqTable != currentDisplay.fqTable {
							tmpQueue = append(tmpQueue, displayQueue[i])
						}
					}
					if len(tmpQueue) > 0 {
						displayQueue = tmpQueue
						currentDisplay = displayQueue[0]
						if currentDisplay.status == "Downloading" {
							setDisplayTable(currentDisplay.fqTable)
						}
						line := fmt.Sprintf("%s: %s", currentDisplay.status, currentDisplay.fqTable)
						lastDisplayLength = len(line)
						fmt.Fprintf(currentDisplay.w, line+"\r")
					} else {
						currentDisplay.fqTable = ""
						setDisplayTable(currentDisplay.fqTable)
					}
				}
			}
		} else {
			if len(displayQueue) == 0 {
				displayQueue = append(displayQueue, displayInfo)
			} else {
				var tableInQueue bool
				for i := 0; i < len(displayQueue); i++ {
					if displayQueue[i].fqTable == displayInfo.fqTable {
						displayQueue[i] = displayInfo
						tableInQueue = true
					}
				}
				if !tableInQueue {
					displayQueue = append(displayQueue, displayInfo)
				}
			}
		}
	}
}
display receives display events and queues events to make printing sane
package main
import (
	"bufio"
	"bytes"
	"database/sql"
	"encoding/json"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"net/http"
	"os"
	"path"
	"path/filepath"
	"runtime"
	"strings"
	"sync"
	"text/tabwriter"
	"time"
	"github.com/joshuaprunier/mysqlUTF8"
	"github.com/klauspost/pgzip"
	"golang.org/x/net/html"
)
type (
	clientConfigStruct struct {
		triteServerURL          string
		triteServerPort         string
		triteMaxConnections     int
		errorLogFile            string
		minDownloadProgressSize int64
		gz                      bool
	}
	downloadInfoStruct struct {
		db            *sql.DB
		taburl        string
		backurl       string
		gzurl         string
		schema        string
		table         string
		encodedSchema string
		encodedTable  string
		mysqldir      string
		uid           int
		gid           int
		engine        string
		extensions    []string
		triteFiles    []string
		version       string
		displayInfo   displayInfoStruct
		displayChan   chan displayInfoStruct
		wgApply       *sync.WaitGroup
	}
	displayInfoStruct struct {
		w       io.Writer
		fqTable string
		status  string
	}
)
const (
	mysqlPerms = 0660
)
var (
	displayTable           string
	errCount               int
	errDownloadUnsupported error
	errDownloadExp         error
	errApplyDrop           error
	errDownloadSize        error
	errApplyCreate         error
	errApplyDiscard        error
	errApplyLock           error
	errApplyRename         error
	errApplyImport         error
	errApplyAnalyze        error
	errApplyUnlock         error
	errObjectApply         error
)
func startClient(clientConfig clientConfigStruct, dbi *mysqlCredentials) {
	db, err := dbi.connect()
	defer db.Close()
	if err != nil {
		fmt.Fprintln(os.Stderr, err)
		os.Exit(1)
	}
	var ignore string
	var maxConnections int
	err = db.QueryRow("show global variables like 'max_connections'").Scan(&ignore, &maxConnections)
	checkErr(err)
	if maxConnections > clientConfig.triteMaxConnections {
		db.SetMaxOpenConns(clientConfig.triteMaxConnections)
	} else {
		fmt.Fprintln(os.Stderr, "MySQL max connections is lower than", clientConfig.triteMaxConnections)
		fmt.Fprintln(os.Stderr, "Increase MySQL's max_connections variable or decrease triteMaxConnections")
		os.Exit(1)
	}
	db.SetMaxIdleConns(0)
	if err != nil {
		fmt.Fprintln(os.Stderr, err)
		os.Exit(1)
	}
	var version string
	err = db.QueryRow("show global variables like 'version'").Scan(&ignore, &version)
	checkErr(err)
	var importFlag string
	if strings.HasPrefix(version, "5.1") || strings.HasPrefix(version, "5.5") {
		err = db.QueryRow("show global variables like '%innodb%import%'").Scan(&importFlag, &ignore)
		checkErr(err)
		_, err = db.Exec("set global " + importFlag + "=1")
		checkErr(err)
	} else if strings.HasPrefix(version, "5.6") || strings.HasPrefix(version, "10") {
	} else {
		fmt.Fprintln(os.Stderr, version, "is not supported")
		os.Exit(1)
	}
	var mysqldir string
	err = db.QueryRow("show variables like 'datadir'").Scan(&ignore, &mysqldir)
	checkErr(err)
	err = ioutil.WriteFile(mysqldir+"/trite_test", []byte("delete\n"), mysqlPerms)
	if err != nil {
		fmt.Fprintln(os.Stderr)
		fmt.Fprintln(os.Stderr, "The MySQL data directory is not writable as this user!")
		fmt.Fprintln(os.Stderr)
		os.Exit(1)
	} else {
		os.Remove(mysqldir + "/trite_test")
	}
	taburl := "http://" + clientConfig.triteServerURL + ":" + clientConfig.triteServerPort + "/tables/"
	backurl := "http://" + clientConfig.triteServerURL + ":" + clientConfig.triteServerPort + "/backups/"
	gzurl := "http://" + clientConfig.triteServerURL + ":" + clientConfig.triteServerPort + "/gz/"
	urls := []string{taburl, backurl}
	for _, url := range urls {
		_, err = http.Head(url)
		if err != nil {
			fmt.Fprintln(os.Stderr)
			fmt.Fprintln(os.Stderr)
			fmt.Fprintln(os.Stderr, "Problem connecting to", url)
			fmt.Fprintln(os.Stderr, "Check that the server is running, port number is correct or that a firewall is not blocking access")
			os.Exit(1)
		}
	}
	base, err := http.Get(taburl)
	checkHTTP(base, taburl)
	defer base.Body.Close()
	checkErr(err)
	schemas := parseAnchor(base)
	var wgDownload sync.WaitGroup
	dl := make(chan downloadInfoStruct)
	go func() {
		for d := range dl {
			downloadTable(clientConfig, d)
			wgDownload.Done()
		}
	}()
	displayChan := make(chan displayInfoStruct)
	go display(displayChan)
	var wgApply sync.WaitGroup
	for _, schema := range schemas {
		checkSchema(db, schema, taburl+path.Join(schema, schema+sqlExtension))
		tablesDir, err := http.Get(taburl + path.Join(schema, "tables"))
		checkHTTP(tablesDir, taburl+path.Join(schema, "tables"))
		defer tablesDir.Body.Close()
		checkErr(err)
		tables := parseAnchor(tablesDir)
		if len(tables) > 0 {
			for _, table := range tables {
				wgDownload.Add(1)
				wgApply.Add(1)
				downloadInfo := downloadInfoStruct{
					db:          db,
					taburl:      taburl,
					backurl:     backurl,
					gzurl:       gzurl,
					schema:      schema,
					table:       table[:len(table)-4],
					mysqldir:    mysqldir,
					uid:         dbi.uid,
					gid:         dbi.gid,
					version:     version,
					displayChan: displayChan,
					wgApply:     &wgApply,
				}
				if mysqlUTF8.NeedsEncoding(downloadInfo.schema) {
					downloadInfo.encodedSchema = mysqlUTF8.EncodeFilename(downloadInfo.schema)
				}
				if mysqlUTF8.NeedsEncoding(downloadInfo.table) {
					downloadInfo.encodedTable = mysqlUTF8.EncodeFilename(downloadInfo.table)
				}
				dl <- downloadInfo
			}
		}
	}
	wgDownload.Wait()
	wgApply.Wait()
	time.Sleep(1 * time.Millisecond)
	fmt.Println()
	objectTypes := []string{"trigger", "view", "procedure", "function"}
	for _, schema := range schemas {
		for _, objectType := range objectTypes {
			applyObjects(db, clientConfig, objectType, schema, taburl)
		}
	}
	if importFlag != "" {
		_, err = db.Exec("set global " + importFlag + "=0")
	}
	errCount := getErrCount()
	if errCount > 0 {
		f, err := os.OpenFile(clientConfig.errorLogFile, os.O_WRONLY|os.O_APPEND, 0644)
		checkErr(err)
		l := log.New(f, "", log.LstdFlags)
		for i := 0; i < 10; i++ {
			l.Println()
		}
		f.Close()
		fmt.Println()
		fmt.Println("! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ")
		fmt.Println(errCount, "errors were encountered")
		fmt.Println("Check", clientConfig.errorLogFile, "for more details")
		fmt.Println("! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ")
	}
}
func getErrCount() int {
	return errCount
}
func incErrCount() {
	var mu sync.Mutex
	mu.Lock()
	errCount++
	mu.Unlock()
}
func getDisplayTable() string {
	return displayTable
}
func setDisplayTable(table string) {
	var mu sync.Mutex
	mu.Lock()
	displayTable = table
	mu.Unlock()
}
func checkHTTP(r *http.Response, url string) {
	if r.StatusCode != 200 {
		fmt.Println(r.StatusCode, "returned from:", url)
		os.Exit(1)
	}
}
func parseAnchor(r *http.Response) []string {
	var txt []string
	tok := html.NewTokenizer(r.Body)
	for {
		tt := tok.Next()
		if tt == html.ErrorToken {
			break
		}
		if tt == html.TextToken {
			a := tok.Raw()
			if a[0] != 10 {
				txt = append(txt, string(bytes.Trim(a, "/")))
			}
		}
	}
	return txt
}
func checkSchema(db *sql.DB, schema string, schemaCreateURL string) {
	var exists string
	err := db.QueryRow("show databases like '" + schema + "'").Scan(&exists)
	if err != nil {
		resp, err := http.Get(schemaCreateURL)
		checkHTTP(resp, schemaCreateURL)
		defer resp.Body.Close()
		checkErr(err)
		stmt, _ := ioutil.ReadAll(resp.Body)
		_, err = db.Exec(string(stmt))
		checkErr(err)
	}
}