text:
package ddevapp

import (
	"bufio"
	"embed"
	"encoding/json"
	"fmt"
	"github.com/drud/ddev/pkg/archive"
	"github.com/drud/ddev/pkg/dockerutil"
	"github.com/drud/ddev/pkg/exec"
	"github.com/drud/ddev/pkg/fileutil"
	"github.com/drud/ddev/pkg/globalconfig"
	"github.com/drud/ddev/pkg/nodeps"
	"github.com/drud/ddev/pkg/output"
	"github.com/drud/ddev/pkg/util"
	"github.com/drud/ddev/pkg/version"
	"github.com/drud/ddev/pkg/versionconstants"
	docker "github.com/fsouza/go-dockerclient"
	"github.com/pkg/errors"
	"os"
	osexec "os/exec"
	"path"
	"path/filepath"
	"runtime"
	"strings"
	"time"
	"unicode"
)

const mutagenSignatureLabelName = `com.ddev.volume-signature`

// SetMutagenVolumeOwnership chowns the volume in use to the current user.
// The mutagen volume is mounted both in /var/www (where it gets used) and
// also on /tmp/project_mutagen (where it can be chowned without accidentally hitting
// lots of bind-mounted files).
func SetMutagenVolumeOwnership(app *DdevApp) error {
	// Make sure that if we have a volume mount it's got proper ownership
	uidStr, gidStr, _ := util.GetContainerUIDGid()
	util.Debug("chowning mutagen docker volume for user %s", uidStr)
	_, _, err := app.Exec(
		&ExecOpts{
			Dir: "/tmp",
			Cmd: fmt.Sprintf("sudo chown -R %s:%s /tmp/project_mutagen", uidStr, gidStr),
		})
	if err != nil {
		util.Warning("Failed to chown mutagen volume: %v", err)
	}
	util.Debug("done chowning mutagen docker volume; result=%v", err)

	return err
}

// MutagenSyncName transforms a projectname string into
// an acceptable mutagen sync "name"
// See restrictions on sync name at https://mutagen.io/documentation/introduction/names-labels-identifiers
// The input must be a valid DNS name (valid ddev project name)
func MutagenSyncName(name string) string {
	name = strings.ReplaceAll(name, ".", "")
	if len(name) > 0 && unicode.IsNumber(rune(name[0])) {
		name = "a" + name
	}
	return name
}

// TerminateMutagenSync destroys a mutagen sync session
// It is not an error if the sync session does not exist
func TerminateMutagenSync(app *DdevApp) error {
	syncName := MutagenSyncName(app.Name)
	if MutagenSyncExists(app) {
		_, err := exec.RunHostCommand(globalconfig.GetMutagenPath(), "sync", "terminate", syncName)
		if err != nil {
			return err
		}
		util.Debug("Terminated mutagen sync session '%s'", syncName)
	}
	return nil
}

// PauseMutagenSync pauses a mutagen sync session
func PauseMutagenSync(app *DdevApp) error {
	syncName := MutagenSyncName(app.Name)
	if MutagenSyncExists(app) {
		_, err := exec.RunHostCommand(globalconfig.GetMutagenPath(), "sync", "pause", syncName)
		if err != nil {
			return err
		}
		util.Debug("Paused mutagen sync session '%s'", syncName)
	}
	return nil
}

// SyncAndPauseMutagenSession syncs and pauses a mutagen sync session
func SyncAndPauseMutagenSession(app *DdevApp) error {
	if app.Name == "" {
		return fmt.Errorf("No app.Name provided to SyncAndPauseMutagenSession")
	}
	syncName := MutagenSyncName(app.Name)

	projStatus, _ := app.SiteStatus()

	if !MutagenSyncExists(app) {
		return nil
	}

	mutagenStatus, shortResult, longResult, err := app.MutagenStatus()
	if err != nil {
		return fmt.Errorf("MutagenStatus failed, rv=%v, shortResult=%s, longResult=%s, err=%v", mutagenStatus, shortResult, longResult, err)
	}

	// We don't want to flush if the web container isn't running
	// because mutagen flush will hang forever - disconnected
	if projStatus == SiteRunning && (mutagenStatus == "ok" || mutagenStatus == "problems") {
		err := app.MutagenSyncFlush()
		if err != nil {
			util.Error("Error on 'mutagen sync flush %s': %v", syncName, err)
		}
	}
	err = PauseMutagenSync(app)
	return err
}

// GetMutagenConfigFilePath returns the canonical location where the mutagen.yml lives
func GetMutagenConfigFilePath(app *DdevApp) string {
	return filepath.Join(app.GetConfigPath("mutagen"), "mutagen.yml")
}

// GetMutagenConfigFile looks to see if there's a project .mutagen.yml
// If nothing is found, returns empty
func GetMutagenConfigFile(app *DdevApp) string {
	projectConfig := GetMutagenConfigFilePath(app)
	if fileutil.FileExists(projectConfig) {
		return projectConfig
	}
	return ""
}

// CreateOrResumeMutagenSync creates or resumes a sync session
// It detects problems with the sync and errors if there are problems
func CreateOrResumeMutagenSync(app *DdevApp) error {
	syncName := MutagenSyncName(app.Name)
	configFile := GetMutagenConfigFile(app)
	if configFile != "" {
		util.Debug("Using mutagen config file %s", configFile)
	}

	container, err := GetContainer(app, "web")
	if err != nil {
		return err
	}
	if container == nil {
		return fmt.Errorf("web container for %s not found", app.Name)
	}
	if container.State != "running" {
		// TODO: Improve or debug this temporary debug usage
		util.Warning("web container is not running, logs follow")
		logsErr := app.Logs("web", false, false, "100")
		if logsErr != nil {
			util.Warning("error from getting logs: %v", logsErr)
		}
		return fmt.Errorf("Cannot start mutagen sync because web container is not running: %v", container)
	}

	sessionExists, err := mutagenSyncSessionExists(app)
	if err != nil {
		return err
	}
	if sessionExists {
		util.Debug("Resume mutagen sync if session already exists")
		err := ResumeMutagenSync(app)
		if err != nil {
			return err
		}
	} else {
		vLabel, err := GetMutagenVolumeLabel(app)
		if err != nil {
			return err
		}
		// TODO: Consider using a function to specify the docker beta
		args := []string{"sync", "create", app.AppRoot, fmt.Sprintf("docker:/%s/var/www/html", container.Names[0]), "--no-global-configuration", "--name", syncName, "--label", mutagenSignatureLabelName + "=" + vLabel}
		if configFile != "" {
			args = append(args, fmt.Sprintf(`--configuration-file=%s`, configFile))
		}
		// On Windows, permissions can't be inferred from what is on the host side, so just force 777 for
		// most things
		if runtime.GOOS == "windows" {
			args = append(args, []string{"--permissions-mode=manual", "--default-file-mode-beta=0777", "--default-directory-mode-beta=0777"}...)
		}
		util.Debug("Creating mutagen sync: mutagen %v", args)
		out, err := exec.RunHostCommand(globalconfig.GetMutagenPath(), args...)
		if err != nil {
			return fmt.Errorf("Failed to mutagen %v (%v), output=%s", args, err, out)
		}
	}

	util.Debug("Flushing mutagen sync session '%s'", syncName)
	flushErr := make(chan error, 1)
	stopGoroutine := make(chan bool, 1)
	firstOutputReceived := make(chan bool, 1)
	defer close(flushErr)
	defer close(stopGoroutine)
	defer close(firstOutputReceived)

	go func() {
		err = app.MutagenSyncFlush()
		flushErr <- err
		return
	}()

	// In tests or other non-interactive environments we don't need to show the
	// mutagen sync monitor output (and it fills up the test logs)

	if os.Getenv("DDEV_NONINTERACTIVE") != "true" {
		go func() {
			previousStatus := ""
			curStatus := ""
			sigSent := false
			cmd := osexec.Command(globalconfig.GetMutagenPath(), "sync", "monitor", syncName)
			stdout, _ := cmd.StdoutPipe()
			err = cmd.Start()
			buf := bufio.NewReader(stdout)
			for {
				select {
				case <-stopGoroutine:
					_ = cmd.Process.Kill()
					_, _ = cmd.Process.Wait()
					return
				default:
					line, err := buf.ReadBytes('\r')
					if err != nil {
						return
					}
					l := string(line)
					if strings.HasPrefix(l, "Status:") {
						// If we haven't already notified that output is coming in,
						// then notify.
						if !sigSent {
							firstOutputReceived <- true
							sigSent = true
							_, _ = fmt.Fprintf(os.Stderr, "\n")
						}

						_, _ = fmt.Fprintf(os.Stderr, "%s", l)
						t := strings.Replace(l, " ", "", 2)
						c := strings.Split(t, " ")
						curStatus = c[0]
						if previousStatus != curStatus {
							_, _ = fmt.Fprintf(os.Stderr, "\n")
						}
						previousStatus = curStatus
					}
				}
			}
		}()
	}

	outputComing := false
	for {
		select {
		// Complete when the MutagenSyncFlush() completes
		case err = <-flushErr:
			return err
		case outputComing = <-firstOutputReceived:

		// If we haven't yet received any "Status:" output, do a dot every second
		case <-time.After(1 * time.Second):
			if !outputComing {
				_, _ = fmt.Fprintf(os.Stderr, ".")
			}
		}
	}
}

func ResumeMutagenSync(app *DdevApp) error {
	args := []string{"sync", "resume", MutagenSyncName(app.Name)}
	util.Debug("Resuming mutagen sync: mutagen %v", args)
	out, err := exec.RunHostCommand(globalconfig.GetMutagenPath(), args...)
	if err != nil {
		return fmt.Errorf("Failed to mutagen %v (%v), output=%s", args, err, out)
	}
	return nil
}

// mutagenSyncSessionExists determines whether an appropriate mutagen sync session already exists
// if it finds one with invalid label, it destroys the existing session.
func mutagenSyncSessionExists(app *DdevApp) (bool, error) {
	syncName := MutagenSyncName(app.Name)
	res, err := exec.RunHostCommandSeparateStreams(globalconfig.GetMutagenPath(), "sync", "list", "--template", "{{ json (index . 0) }}", syncName)
	if err != nil {
		if exitError, ok := err.(*osexec.ExitError); ok {
			// If we got an error, but it's that there were no sessions, return false, no err
			if strings.Contains(string(exitError.Stderr), "did not match any sessions") {
				return false, nil
			}
		}
		return false, err
	}
	session := make(map[string]interface{})
	err = json.Unmarshal([]byte(res), &session)
	if err != nil {
		return false, fmt.Errorf("failed to unmarshall mutagen sync list results '%v': %v", res, err)
	}

	// Find out if mutagen session labels has label we found in docker volume
	if l, ok := session["labels"].(map[string]interface{}); ok {
		vLabel, vLabelErr := GetMutagenVolumeLabel(app)
		if s, ok := l[mutagenSignatureLabelName]; ok && vLabelErr == nil && vLabel != "" && vLabel == s {
			return true, nil
		}
		// If we happen to find a mutagen session without matching signature, terminate it.
		_ = TerminateMutagenSync(app)
	}
	return false, nil
}

// MutagenStatus checks to see if there is an error case in mutagen
// We don't want to do a flush yet in that case.
// Note that the available statuses are at https://github.com/mutagen-io/mutagen/blob/master/pkg/synchronization/state.go#L9
// in func (s Status) Description()
// Can return any of those or "nosession" (with more info) if we didn't find a session at all
func (app *DdevApp) MutagenStatus() (status string, shortResult string, mapResult map[string]interface{}, err error) {
	syncName := MutagenSyncName(app.Name)

	mutagenDataDirectory := os.Getenv("MUTAGEN_DATA_DIRECTORY")
	fullJSONResult, err := exec.RunHostCommandSeparateStreams(globalconfig.GetMutagenPath(), "sync", "list", "--template", `{{ json (index . 0) }}`, syncName)
	if err != nil {
		stderr := ""
		if exitError, ok := err.(*osexec.ExitError); ok {
			stderr = string(exitError.Stderr)
		}
		return fmt.Sprintf("nosession for MUTAGEN_DATA_DIRECTORY=%s", mutagenDataDirectory), fullJSONResult, nil, fmt.Errorf("failed to mutagen sync list %s: stderr='%s', err=%v", syncName, stderr, err)
	}
	session := make(map[string]interface{})
	err = json.Unmarshal([]byte(fullJSONResult), &session)
	if err != nil {
		return fmt.Sprintf("nosession for MUTAGEN_DATA_DIRECTORY=%s; failed to unmarshall mutagen sync list results '%v'", mutagenDataDirectory, fullJSONResult), fullJSONResult, nil, err
	}

	if paused, ok := session["paused"].(bool); ok && paused == true {
		return "paused", "paused", session, nil
	}
	var ok bool
	if shortResult, ok = session["status"].(string); !ok {
		return "failing", shortResult, session, fmt.Errorf("mutagen sessions may be in invalid state, please `ddev mutagen reset`")
	}
	shortResult = session["status"].(string)

	// In the odd case where somebody enabled mutagen when it wasn't actually running
	// show a simpler result
	mounted, err := IsMutagenVolumeMounted(app)
	if !mounted {
		return "not enabled", "", session, nil
	}
	if err != nil {
		return "", "", nil, err
	}

	problems := false
	if alpha, ok := session["alpha"].(map[string]interface{}); ok {
		if _, ok = alpha["scanProblems"]; ok {
			problems = true
		}
	}
	if beta, ok := session["beta"].(map[string]interface{}); ok {
		if _, ok = beta["scanProblems"]; ok {
			problems = true
		}
	}
	if _, ok := session["conflicts"]; ok {
		problems = true
	}

	// We're going to assume that if it's applying changes things are still OK,
	// even though there may be a whole list of problems.
	// States from json are in https://github.com/mutagen-io/mutagen/blob/bc07f2f0f3f0aba0aff0514bd4739d75444091fe/pkg/synchronization/state.go#L47-L79
	switch shortResult {
	case "paused":
		return "paused", shortResult, session, nil
	case "transitioning":
		fallthrough
	case "staging-alpha":
		fallthrough
	case "connecting-beta":
		fallthrough
	case "staging-beta":
		fallthrough
	case "reconciling":
		fallthrough
	case "scanning":
		fallthrough
	case "saving":
		fallthrough
	case "watching":
		if !problems {
			status = "ok"
		} else {
			status = "problems"
		}
		return status, shortResult, session, nil
	}
	return "failing", shortResult, session, nil
}

// MutagenSyncFlush performs a mutagen sync flush, waits for result, and checks for errors
func (app *DdevApp) MutagenSyncFlush() error {
	status, _ := app.SiteStatus()
	if status == SiteRunning && app.IsMutagenEnabled() {
		syncName := MutagenSyncName(app.Name)
		if !MutagenSyncExists(app) {
			return errors.Errorf("Mutagen sync session '%s' does not exist", syncName)
		}
		if status, shortResult, session, err := app.MutagenStatus(); err == nil {
			switch status {
			case "paused":
				util.Debug("mutagen sync %s is paused, so not flushing", syncName)
				return nil
			case "failing":
				util.Warning("mutagen sync session %s has status '%s': shortResult='%v', err=%v, session contents='%v'", syncName, status, shortResult, err, session)
			default:
				out, err := exec.RunHostCommand(globalconfig.GetMutagenPath(), "sync", "flush", syncName)
				if err != nil {
					return fmt.Errorf("mutagen sync flush %s failed, output=%s, err=%v", syncName, out, err)
				}
			}
		}

		status, _, _, err := app.MutagenStatus()
		if (status != "ok" && status != "problems" && status != "paused" && status != "failing") || err != nil {
			return err
		}
		util.Debug("Flushed mutagen sync session '%s'", syncName)
	}
	return nil
}

// MutagenSyncExists detects whether the named sync exists
func MutagenSyncExists(app *DdevApp) bool {
	syncName := MutagenSyncName(app.Name)

	if !fileutil.FileExists(globalconfig.GetMutagenPath()) {
		return false
	}
	// List syncs with this name that also match appropriate labels
	c := []string{globalconfig.GetMutagenPath(), "sync", "list", syncName}
	out, err := exec.RunHostCommand(c[0], c[1:]...)
	if err != nil && !strings.Contains(out, "Error: unable to locate requested sessions") {
		util.Warning("%v failed: %v output=%v", c, err, out)
	}
	return err == nil
}

// DownloadMutagen gets the mutagen binary and related and puts it into
// ~/.ddev/.bin
func DownloadMutagen() error {
	StopMutagenDaemon()
	flavor := runtime.GOOS + "_" + runtime.GOARCH
	globalMutagenDir := filepath.Dir(globalconfig.GetMutagenPath())
	destFile := filepath.Join(globalMutagenDir, "mutagen.tgz")
	mutagenURL := fmt.Sprintf("https://github.com/mutagen-io/mutagen/releases/download/v%s/mutagen_%s_v%s.tar.gz", versionconstants.RequiredMutagenVersion, flavor, versionconstants.RequiredMutagenVersion)
	output.UserOut.Printf("Downloading %s ...", mutagenURL)

	// Remove the existing file. This may help on macOS to prevent the Gatekeeper's
	// caching bug from confusing with a previously downloaded file?
	// Discussion in https://github.com/mutagen-io/mutagen/issues/290#issuecomment-906612749
	_ = os.Remove(globalconfig.GetMutagenPath())

	_ = os.MkdirAll(globalMutagenDir, 0777)
	err := util.DownloadFile(destFile, mutagenURL, "true" != os.Getenv("DDEV_NONINTERACTIVE"))
	if err != nil {
		return err
	}
	output.UserOut.Printf("Download complete.")

	err = archive.Untar(destFile, globalMutagenDir, "")
	_ = os.Remove(destFile)
	if err != nil {
		return err
	}
	err = os.Chmod(globalconfig.GetMutagenPath(), 0755)
	if err != nil {
		return err
	}

	// Stop daemon in case it was already running somewhere else
	StopMutagenDaemon()
	return nil
}

// StopMutagenDaemon will try to stop a running mutagen daemon
// But no problem if there wasn't one
func StopMutagenDaemon() {
	if fileutil.FileExists(globalconfig.GetMutagenPath()) {
		mutagenDataDirectory := os.Getenv("MUTAGEN_DATA_DIRECTORY")
		out, err := exec.RunHostCommand(globalconfig.GetMutagenPath(), "daemon", "stop")
		if err != nil && !strings.Contains(out, "unable to connect to daemon") {
			util.Warning("Unable to stop mutagen daemon: %v; MUTAGEN_DATA_DIRECTORY=%s", err, mutagenDataDirectory)
		}
		util.Success("Stopped mutagen daemon")
	}
}

// StartMutagenDaemon will make sure the daemon is running
func StartMutagenDaemon() {
	if fileutil.FileExists(globalconfig.GetMutagenPath()) {
		out, err := exec.RunHostCommand(globalconfig.GetMutagenPath(), "daemon", "start")
		if err != nil {
			util.Warning("Failed to run mutagen daemon start: %v, out=%s", err, out)
		}
	}
}

// DownloadMutagenIfNeeded downloads the proper version of mutagen
// if it's either not yet installed or has the wrong version.
func DownloadMutagenIfNeeded(app *DdevApp) error {
	if !app.IsMutagenEnabled() {
		return nil
	}
	err := os.MkdirAll(globalconfig.GetMutagenDataDirectory(), 0755)
	if err != nil {
		return err
	}
	curVersion, err := version.GetLiveMutagenVersion()
	if err != nil || curVersion != versionconstants.RequiredMutagenVersion {
		err = DownloadMutagen()
		if err != nil {
			return err
		}
	}
	return nil
}

// MutagenReset stops (with flush), removes the docker volume, starts again (with flush)
func MutagenReset(app *DdevApp) error {
	if app.IsMutagenEnabled() {
		err := app.Stop(false, false)
		if err != nil {
			return errors.Errorf("Failed to stop project %s: %v", app.Name, err)
		}
		err = dockerutil.RemoveVolume(GetMutagenVolumeName(app))
		if err != nil {
			return err
		}
		util.Debug("Removed docker volume %s", GetMutagenVolumeName(app))
		err = TerminateMutagenSync(app)
		if err != nil {
			return err
		}
		util.Debug("Terminated mutagen sync session %s", MutagenSyncName(app.Name))
	}
	return nil
}

// GetMutagenVolumeName returns the name for the mutagen docker volume
func GetMutagenVolumeName(app *DdevApp) string {
	return app.Name + "_" + "project_mutagen"
}

// MutagenMonitor shows the output of `mutagen sync monitor <syncName>`
func MutagenMonitor(app *DdevApp) {
	syncName := MutagenSyncName(app.Name)

	// This doesn't actually return; you have to <ctrl-c> to end it
	c := osexec.Command(globalconfig.GetMutagenPath(), "sync", "monitor", syncName)
	// We only need all three of these because of Windows behavior on git-bash with no pty
	c.Stdout = os.Stdout
	c.Stderr = os.Stderr
	c.Stdin = os.Stdin
	_ = c.Run()
}

//go:embed mutagen_config_assets
var mutagenConfigAssets embed.FS

// GenerateMutagenYml generates the .ddev/mutagen.yml
func (app *DdevApp) GenerateMutagenYml() error {
	// Prevent running as root for most cases
	// We really don't want ~/.ddev to have root ownership, breaks things.
	if os.Geteuid() == 0 {
		util.Warning("not generating mutagen config file because running with root privileges")
		return nil
	}
	mutagenYmlPath := GetMutagenConfigFilePath(app)
	if sigExists, err := fileutil.FgrepStringInFile(mutagenYmlPath, nodeps.DdevFileSignature); err == nil && !sigExists {
		// If the signature doesn't exist, they have taken over the file, so return
		return nil
	}

	c, err := mutagenConfigAssets.ReadFile(path.Join("mutagen_config_assets", "mutagen.yml"))
	if err != nil {
		return err
	}
	content := string(c)

	// It's impossible to use posix-raw on traditional windows.
	// But this means that there will be errors with rooted symlinks in the container on windows
	symlinkMode := "posix-raw"
	if runtime.GOOS == "windows" {
		symlinkMode = "portable"
	}
	err = os.MkdirAll(filepath.Dir(mutagenYmlPath), 0755)
	if err != nil {
		return err
	}

	uploadDir := ""
	if app.GetUploadDir() != "" {
		uploadDir = path.Join(app.Docroot, app.GetUploadDir())
	}

	templateMap := map[string]interface{}{
		"SymlinkMode": symlinkMode,
		"UploadDir":   uploadDir,
	}
	// If no bind mounts, then we can't ignore UploadDir, must sync it
	if globalconfig.DdevGlobalConfig.NoBindMounts {
		templateMap["UploadDir"] = ""
	}

	err = fileutil.TemplateStringToFile(content, templateMap, mutagenYmlPath)
	return err
}

// IsMutagenVolumeMounted checks to see if the mutagen volume is mounted
func IsMutagenVolumeMounted(app *DdevApp) (bool, error) {
	client := dockerutil.GetDockerClient()
	container, err := dockerutil.FindContainerByName("ddev-" + app.Name + "-web")
	// If there is no web container found, the volume is not mounted
	if err != nil || container == nil {
		return false, nil
	}
	inspect, err := client.InspectContainerWithOptions(docker.InspectContainerOptions{
		ID: container.ID,
	})
	if err != nil {
		return false, err
	}
	for _, m := range inspect.Mounts {
		if m.Name == app.Name+"_project_mutagen" {
			return true, nil
		}
	}
	return false, nil
}

// IsMutagenEnabled returns true if mutagen is enabled locally or globally
// It's also required and set if NoBindMounts is set, since we have to have a way
// to get code on there.
func (app *DdevApp) IsMutagenEnabled() bool {
	return app.MutagenEnabled || app.MutagenEnabledGlobal || globalconfig.DdevGlobalConfig.NoBindMounts
}

// GetMutagenVolumeLabel returns the com.ddev.volume-signature on the project_mutagen docker volume
func GetMutagenVolumeLabel(app *DdevApp) (string, error) {
	labels, err := dockerutil.VolumeLabels(GetMutagenVolumeName(app))
	if err != nil {
		return "", err
	}
	if labels != nil {
		if l, ok := labels[mutagenSignatureLabelName]; ok {
			return l, nil
		}
	}
	return "", nil
}

// CheckMutagenVolumeSyncCompatibility checks to see if the mutagen label and volume label
// are the same.
// Compatible if:
//   - No volume (or no volume and no mutagen sync session)
//   - Volume and mutagen sync exist and Volume label matches mutagen label
//
// Not compatible if
//   - Volume and mutagen sync exist and have different labels
//   - Volume exists (with label) but there's no mutagen sync session matching it. In this case we'd want
//     to start from scratch with a new volume and sync, so we get authoritative files from alpha (host)
//   - Volume has a label that is not based on this docker context.
//
// Return ok, info, where ok true if compatible, info gives reasoning
func CheckMutagenVolumeSyncCompatibility(app *DdevApp) (ok bool, volumeExists bool, info string) {
	mutagenSyncExists := MutagenSyncExists(app)
	volumeLabel, volumeLabelErr := GetMutagenVolumeLabel(app)
	dockerHostID := dockerutil.GetDockerHostID()
	mutagenLabel := ""
	var mutagenSyncLabelErr error

	volumeExists = !(volumeLabelErr != nil && errors.Is(docker.ErrNoSuchVolume, volumeLabelErr))

	if mutagenSyncExists {
		mutagenLabel, mutagenSyncLabelErr = GetMutagenSyncLabel(app)
		if mutagenSyncLabelErr != nil {
			util.Warning("mutagen sync %s exists but unable to get label: %v", app.Name, mutagenSyncLabelErr)
		}
	}
	switch {
	// If there is no volume, everything is fine, proceed.
	case !volumeExists:
		return true, volumeExists, "no docker volume exists, so compatible"
	case mutagenSyncLabelErr != nil:
		return false, volumeExists, "mutagen sync session exists but does not have label"
	// If the labels do not have the current context as first part of label, we have trouble.
	case !strings.HasPrefix(volumeLabel, dockerHostID) || !strings.HasPrefix(mutagenLabel, dockerHostID):
		return false, volumeExists, fmt.Sprintf("volume label '%s' or sync label '%s' does not start with current dockerHostID (%s)", volumeLabel, mutagenLabel, dockerHostID)
	// if we have labels for both and they match, it's all fine.
	case mutagenLabel == volumeLabel:
		return true, volumeExists, fmt.Sprintf("volume and mutagen sync session have the same label: %s", volumeLabel)
	}

	return false, volumeExists, fmt.Sprintf("CheckMutagenVolumeSyncCompatibility: currentDockerContext=%s mutagenLabel='%s', volumeLabel='%s', mutagenSyncLabelErr='%v', volumeLabelErr='%v'", dockerutil.DockerContext, mutagenLabel, volumeLabel, mutagenSyncLabelErr, volumeLabelErr)
}

// GetMutagenSyncLabel gets the com.ddev.volume-signature label from an existing sync session
func GetMutagenSyncLabel(app *DdevApp) (string, error) {
	status, _, mapResult, err := app.MutagenStatus()

	if strings.HasPrefix(status, "nosession") || err != nil {
		return "", fmt.Errorf("no session %s found: %v", MutagenSyncName(app.Name), status)
	}
	if labels, ok := mapResult["labels"].(map[string]interface{}); ok {
		if label, ok := labels[mutagenSignatureLabelName].(string); ok {
			return label, nil
		}
	}
	return "", fmt.Errorf("sync session label not found for sync session %s", MutagenSyncName(app.Name))
}

// TerminateAllMutagenSync terminates all sync sessions
func TerminateAllMutagenSync() {
	out, err := exec.RunHostCommand(globalconfig.GetMutagenPath(), "sync", "terminate", "-a")
	if err != nil {
		util.Warning("could not terminate all mutagen sessions (mutagen sync terminate -a), output=%s, err=%v", out, err)
	}
}

// GetDefaultMutagenVolumeSignature gets a new volume signature to be applied to mutagen volume
func GetDefaultMutagenVolumeSignature(app *DdevApp) string {
	return fmt.Sprintf("%s-%v", dockerutil.GetDockerHostID(), time.Now().Unix())
}

// CheckMutagenUploadDir just tells people if they are using mutagen without upload_dir
func CheckMutagenUploadDir(app *DdevApp) {
	if app.IsMutagenEnabled() && app.GetUploadDir() == "" {
		util.Warning("You have mutagen enabled and your '%s' project type doesn't have an upload_dir set.", app.Type)
		util.Warning("For faster startup and less disk usage,\nset upload_dir to where your user-generated files are stored.")
	}
}


text:
/*  Part of SWI-Prolog

    Author:        Anne Ogborn
    WWW:           http://www.swi-prolog.org

    This program is free software; you can redistribute it and/or
    modify it under the terms of the GNU General Public License
    as published by the Free Software Foundation; either version 2
    of the License, or (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public
    License along with this library; if not, write to the Free Software
    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

    As a special exception, if you link this library with other files,
    compiled with a Free Software compiler, to produce an executable, this
    library does not by itself cause the resulting executable to be covered
    by the GNU General Public License. This exception does not however
    invalidate any other reasons why the executable file might be covered by
    the GNU General Public License.
*/

:-module(did_you_know,
	 [ did_you_know_script//1,
	   did_you_know//0
	 ]).

/**  <module>  Interesting snippets about SWI-Prolog

This module generates the 'Did You Know   blah blah' bit that appears on
every pldoc and SWI-Prolog website page.
*/

:- use_module(library(http/html_write)).
:- use_module(library(http/http_dispatch)).
:- use_module(library(random)).
:- use_module(news).
:- use_module(holidays).
:- use_module(library(http/js_write)).

:- http_handler(root(dyk), did_you_know, []).

%%	did_you_know(+Request)
%
%	Reply with a non-cacheable DYK fragment.

did_you_know(_Request) :-
	disable_client_cache,
	reply_html_page(plain,
			title('SWI-Prolog Did You Know'),
			\did_you_know).

disable_client_cache :-
	format('Cache-Control: no-cache, no-store, must-revalidate\r\n\c
                Pragma: no-cache\r\n\c
		Expires: 0\r\n').

%%	did_you_know_script(+Id)//
%
%	Emit script to fetch DYK through AJAX

did_you_know_script(Id) -->
	html('Did you know ... '),
	{ http_link_to_id(did_you_know, [], HREF) },
	js_script({|javascript(Id, HREF)||
		   $(function() {
		     $.ajax({ url: HREF,
			      success: function(data) {
			        $("#"+Id).html(data);
			      }
		            });
		   });
		  |}).

%%	did_you_know//
%
%	Generate a DYK fragment.

did_you_know -->
	{ maybe(0.5) },
	random_news, !.
did_you_know -->
	{ todays_holiday(april_fools_day),
	  maybe(0.1)
	},
	random_silly_hint.
did_you_know -->
	random_hint.

random_silly_hint -->
	{ predicate_property(afdyk(_,_), number_of_clauses(N)),
	  (   debugging(dyk(Id)),
	      integer(Id)
	  ->  true
	  ;   random_between(1, N, Id)
	  ),
	  afdyk(Id, Saying)
	},
	say_af_saying(Saying).

say_af_saying(Text-Link) -->
	{ link(Link, HREF) },
	html([ span(class(lbl), 'Did you know?'),
	       ' ',
	       span(id(dyknow), a(href(HREF), Text))
	     ]).
say_af_saying(news(Text-Link)) -->
	{ link(Link, HREF) },
	html([ span(class(lbl), 'News:'),
	       ' ',
	       span(id(dyknow), a(href(HREF), Text))
	     ]).
say_af_saying(news(Text)) -->
	html([ span(class(lbl), 'News:'),
	       ' ',
	       span(id(dyknow), Text)
	     ]).
say_af_saying(Text) -->
	html([ span(class(lbl), 'Did you know?'),
	       ' ',
	       span(id(dyknow), Text)
	     ]).

random_hint -->
	{ predicate_property(dyk(_,_), number_of_clauses(N)),
	  (   debugging(dyk(Id)),
	      integer(Id)
	  ->  true
	  ;   random_between(1, N, Id)
	  ),
	  dyk(Id, Saying),
	  (   Saying = (Text-Link)
	  ->  link(Link, HREF),
	      Info = a(href(HREF), Text)
	  ;   Info = Saying
	  )
	},
	html([ span(class(lbl), 'Did you know?'),
	       ' ',
	       span(id(dyknow), Info)
	     ]).

link(section(Id), HREF) :- !,
	http_link_to_id(pldoc_man, [section=Id], HREF).
link(package(Name), HREF) :- !,
	file_name_extension(Name, html, File),
	http_link_to_id(pldoc_package, path_postfix(File), HREF).
link(pack(Name), HREF) :- !,
	http_link_to_id(pack_list, [p(Name)], HREF).
link(HREF, HREF).

%%	dyk(Id, Saying) is nondet
%
%	True if Saying is a DYK exression.  They come in two forms:
%
%	  * HTML - HREF
%	  * HTML
%
%	be careful with line lengths for sayings, 40 chars max
:- multifile  term_expansion/2.

:- nb_setval(dyk_id, 1).
term_expansion(dyk(Saying), dyk(Id, Saying)) :-
	nb_getval(dyk_id, Id),
	Id2 is Id+1,
	nb_setval(dyk_id, Id2).

dyk('SWI-Prolog is 28 years old').
dyk(['the ', b('Profiler'), ' can speed up your code']-section('profiling-predicates')).
dyk('You can hot-swap code').
dyk('SWI-Prolog uses logical update view'-section('update')).
dyk('M-/ does autocomplete in pceEmacs').
dyk('C-c C-c CamelCasesWords in pceEmacs').
dyk('C-c C-- underscores_words_in_pce_emacs').
dyk('C-+ and C-- changes font size in pceEmacs').
dyk('About the nifty drawing program pceDraw').
dyk('Special quasiquote syntax for html and javascript'-section('quasiquotations')).
dyk('Advice for choosing 32/64 bits'-section('64bits')).
dyk('You can configure your environment'-section('initfile')).
dyk(['SWI-Prolog supports the ', b('Snowball'), ' stemmer']-section('snowball')).
dyk(['SWI-Prolog supports ', b(tabling), ' (SLG resolution)']-section('tabling')).
dyk('SWI-Prolog has an RDF Semantic Web Server'-'http://cliopatria.swi-prolog.org/home').
dyk('You can interface C++ to SWI-Prolog'-package('pl2cpp')).
dyk('SWI-Prolog can work with tar archives'-package('archive')).
dyk('This website is written entirely in SWI-Prolog'-package('http')).
dyk(['about the full featured ', b('web framework')]-package('http')).
dyk(['SWI-Prolog can act as an ', b('http client')]-section('http-clients')).
dyk('SWI-Prolog supports PDT, the Prolog Development Tools'-package('pdt')).
dyk('You can get Javadoc style documentation automatically'-package('pldoc')).
dyk(['SWI-Prolog has a ', b('unit test framework')]-package('plunit')).
dyk(['SWI-Prolog has a ', b('Natural Language Processing (NLP)'), ' library']-package('nlp')).
dyk(['SWI-Prolog supports ', b('Google Protocol Buffers')]-package('protobufs')).
dyk('SWI-Prolog talks to R'-package('R')).
dyk(['SWI-Prolog has ', b('powerful Semantic Web tools')]-package('semweb')).
dyk(['SWI-Prolog can ', b('parse SGML/XML')]-package('sgml')).
dyk(['SWI-Prolog has extensive ', b('GIS Support')]-package('space')).
dyk(['SWI-Prolog has support for ', b('large, static tables')]-package('table')).
dyk(['SWI-Prolog supports ', b('TIPC')]-package('tipc')).
dyk(['You can read/write ', b('.zip'), ' files']-package('zlib')).
dyk(['SWI-Prolog can talk to Java,C,C++,Python,and C#']-'/contrib/').
dyk(['You can control ', b('MIDI'), ' on Mac with SWI-Prolog']-'/contrib/SamerAbdallah/index.html').
dyk(['SWI-Prolog has ', b('an OpenGL Interface')]-'/contrib/OpenGL.html').
dyk(['SWI-Prolog is highly ', b('cross platform')]).
dyk('SWI-Prolog has multiple high quality random number generators'-'/contrib/SamerAbdallah/index.html').
dyk('The SWI-Prolog manual is available in printed form'-
    'http://books.google.nl/books/about/SWI_Prolog_Reference_Manual_6_2_2.html?id=q6R3Q3B-VC4C&redir_esc=y').
dyk(['ETALIS ', b('Event Processing'), ' runs on SWI-Prolog']-'http://code.google.com/p/etalis/').
dyk('This website\'s code is available'-'https://github.com/SWI-Prolog/plweb').
dyk(['SWI-Prolog can talk to ', b('Matlab')]-'/contrib/SamerAbdallah/index.html').
dyk(['SWI-Prolog has an active ', b('mailing list')]-'/Mailinglist.html').
dyk(['Jan loves it when you ', b('Report Bugs')]-'https://github.com/SWI-Prolog/issues/issues').
dyk(['You can get ', span(class=colored, 'COLORED'), ' text on the command line']-'/FAQ/ColorConsole.html').
dyk(['SWI-Prolog has a ', b('Nifty IDE')]-'/IDE.html').
dyk(['SWI-Prolog has a ', b('Graphic Debugger')]-'/gtrace.html').
dyk(['Try C-c C-n in pceEmacs']-'/navigator.html').
dyk('Try gxref. from the top level with a large project open'-'/gxref.html').
dyk('XPCE supports a sophisticated styled text engine').
dyk('Your proprietary application can use SWI-Prolog'-'/license.html').
dyk(['SWI-Prolog has an interface to FANN, a foss ', b('Neural Net'), ' library']-'http://leenissen.dk/fann/wp/').
dyk('SWI-Prolog has lots of useful Packages'-'/pack/list').
dyk(['SWI-Prolog can ', b('track open source licenses')]-section(softlicense)).
dyk(['SWI-Prolog has a pack to access ', b('Pubmed Data')]-pack(pubmed)).
dyk(['SWI-Prolog has ', b('Multi-Thread support')]-section(threads)).
dyk(['SWI-Prolog provides ', b('general DCG primitives')]-'/pldoc/doc/swi/library/dcg/basics.pl').
dyk('SWI-Prolog can handle Unix signals'-section(signal)).
dyk('SWI-Prolog can lazily parse a file'-section(pureinput)).
dyk('You can add menus to the swipl-win.exe console in windows'-section(plwin)).
dyk(['SWI-Prolog has a ', b('Profiler')]-section(profile)).
dyk('SWI-Prolog supports DDE on Windows'-section('DDE')).
dyk(['You can create ', b('stand alone exe files'), ' from SWI-Prolog code']-section(runtime)).
dyk('SWI-Prolog supports arbitrarily large integers').
% below here added by AO 2015-01-18
dyk(['There\'s an API to interact with', b('Amazon')]-pack(amazon_api)).
dyk('Nifty call graphs'-pack(callgraph)).
dyk('condition is an alternative to exceptions'-pack(condition)).
dyk('You can train markov chains with BIMS'-pack(bims)).
dyk('blog_core is a nifty CMS for SWI-Prolog'-pack(blog_core)).
dyk('anything in single quotes is an atom').
dyk('Logtalk is now a pack'-pack(logtalk)).
dyk('SWI-Prolog has probabilistic logic'-pack(cplint)).
dyk(['DCG help at lib ',
     a(href='/pldoc/doc/swi/library/dcg/basics.pl', 'dcg/basics'),
     ' & packs dcg_util & dcg_utils']).
dyk('automatic UML->Prolog translation'-pack(dia)).
dyk('docstore is a document oriented DB in SWI-Prolog'-pack(docstore)).
dyk('you can deploy to dotcloud'-pack(dotcloud)).
dyk('CQL makes dealing with SQL easier'-section('/packages/cql.html')).
dyk('you can turn ugraphs into graphml'-pack(graphml)).
dyk('you can turn terms into graphviz (.dot) files'-pack(gvterm)).
dyk('about nifty JoCaml style multithreading'-pack(jolog)).
dyk('the first Prolog interpreter was in Fortran').
dyk('julian pack offers match based dates'-pack(julian)).
dyk('eliminate helper predicates with pack lambda'-pack(lambda)).
dyk('you can parse markdown').
dyk('don\'t use format to print errors'-section(debug)).
dyk('there\'s a simplex library'-section(simplex)).
dyk('use mavis for type checking'-pack(mavis)).
dyk('you can read ODF spreadsheets'-pack(odf_sheet)).
dyk('how to submit a patch'-'/howto/SubmitPatch.html').
dyk('add language:prolog in github search').
dyk('there is an official Docker library for SWI-Prolog'-'/Docker.html').
dyk('there is a Docker image SWISH'-'/Docker.html').
dyk('SWI-Prolog runs on Android Termux'-'/build/Termux.html').

%%	afdyk(Id, Saying) is nondet
%
%	True if Saying is a DYK exression for april fools. They come in
%	four forms:
%
%	  * HTML - HREF
%	  * HTML
%	  * news(HTML - HREF)
%	  * news(HTML)
%
%	be careful with line lengths for sayings, 40 chars max

term_expansion(afdyk(Saying), afdyk(Id, Saying)) :-
	(   predicate_property(afdyk(_,_), number_of_clauses(N))
	->  Id is N+1
	;   Id = 1
	).

afdyk('SWI-Prolog complies with ISO JTC1/SC22/WG4'-'http://cobolstandard.info/wg4/wg4.html').
afdyk('C-c C-q automatically corrects syntax errors'-'/AprilFools.html').
afdyk('SWI-Prolog defaults to EBCDIC'-'/AprilFools.html').
afdyk(news('SWI-Prolog is now available on 9-track tape'-'/AprilFools.html')).
afdyk('pack_install(agi) installs Skynet'-'/AprilFools.html').
afdyk(['SWI-Prolog powers ', a(href='http://java.com' , 'this popular site')]).
afdyk('http_get is (?, ?, +)').
afdyk(news('A SWI-Prolog program beat the world champion in box hockey'-'/AprilFools.html')).
afdyk('8cD'-'https://github.com/Anniepoo/prolog-examples/blob/master/emoticons.pl').
afdyk(news('Bill Joy admits he\'s wrong, urges Prolog'-'/AprilFools.html')).
afdyk(['Prolog actually ', b('IS'), ' good for torturing undergrads']-'/AprilFools.html').
afdyk(news('Colmerauer admits Prolog isn\'t logical at all'-'/AprilFools.html')).
afdyk('about pack(antigravity)'-'/AprilFools.html').
afdyk('SWI-Prolog 7.1.29 requires OSGi and qPID'-'/AprilFools.html').
afdyk('this website powered by windmills'-'/dogfood.html').
afdyk('Nou breekt mijn klomp. Prolog is beter!'-'/AprilFools.html').
afdyk('about pack(klomp)'-'/AprilFools.html').
afdyk('about pack(cheese)'-'/AprilFools.html').
afdyk('about pack(chocolate)'-'/AprilFools.html').
afdyk('about pack(evil)'-pack(evil)).
afdyk('Use Appendix B for clear code'-section(hack)).
afdyk('Early Prologs ended clauses with AMEN'-
      'http://web.archive.org/web/20070703003934/www.lim.univ-mrs.fr/~colmer/ArchivesPublications/HistoireProlog/19november92.pdf').
afdyk('test'-pack(abdcsd)).

redpajama_set_name: RedPajamaGithub


text:
module TeamMembersHelper
end


text:
namespace Torshia.Controllers
{
    using SIS.Framework.ActionResults;
    using SIS.Framework.Attributes.Method;
    using Services.Contracts;

    public class HomeController : BaseController
    {
        private readonly ITasksService tasksService;

        public HomeController(ITasksService tasksService)
        {
            this.tasksService = tasksService;
        }

        [HttpGet]
        public IActionResult Index()
        {
            if (this.IsLoggedIn())
            {
                this.Model["Username"] = this.Identity.Username;
                this.Model["Tasks"] = this.tasksService.AllNonReported();
            }

            return this.View();
        }
    }
}


text:
package org.apache.oozie.coord.input.dependency;

import java.io.IOException;
import java.util.Collection;
import java.util.List;
import java.util.Map;

import org.apache.oozie.CoordinatorActionBean;
import org.apache.oozie.command.CommandException;
import org.apache.oozie.dependency.ActionDependency;
import org.jdom.Element;
import org.jdom.JDOMException;

public interface CoordInputDependency {

    String INTERNAL_VERSION_ID = "V=1";

    /**
     * Adds the input instance list.
     *
     * @param inputEventName the input event name
     * @param inputInstanceList the input instance list
     */
    void addInputInstanceList(String inputEventName, List<CoordInputInstance> inputInstanceList);

    /**
     * Gets the missing dependencies.
     *
     * @return the missing dependencies
     */
    String getMissingDependencies();

    /**
     * Checks if dependencies are meet.
     *
     * @return true, if dependencies are meet
     */
    boolean isDependencyMet();

    /**
     * Checks if is unresolved dependencies met.
     *
     * @return true, if unresolved dependencies are met
     */
    boolean isUnResolvedDependencyMet();

    /**
     * Sets the dependency meet.
     *
     * @param isMissingDependenciesMet the new dependency met
     */
    void setDependencyMet(boolean isMissingDependenciesMet);

    /**
     * Serialize.
     *
     * @return the string
     * @throws IOException Signals that an I/O exception has occurred.
     */
    String serialize() throws IOException;

    /**
     * Gets the missing dependencies as list.
     *
     * @return the missing dependencies as list
     */
    List<String> getMissingDependenciesAsList();

    /**
     * Gets the available dependencies as list.
     *
     * @return the available dependencies as list
     */
    List<String> getAvailableDependenciesAsList();

    /**
     * Sets the missing dependencies.
     *
     * @param missingDependencies the new missing dependencies
     */
    void setMissingDependencies(String missingDependencies);

    /**
     * Adds the un resolved list.
     *
     * @param name the name
     * @param tmpUnresolved the tmp unresolved
     */
    void addUnResolvedList(String name, String tmpUnresolved);

    /**
     * Gets the available dependencies.
     *
     * @param dataSet the data set
     * @return the available dependencies
     */
    List<String> getAvailableDependencies(String dataSet);

    /**
     * Adds the to available dependencies.
     *
     * @param availDepList the avail dep list
     */
    void addToAvailableDependencies(Collection<String> availDepList);

    /**
     * Check push missing dependencies.
     *
     * @param coordAction the coord action
     * @param registerForNotification the register for notification
     * @return the action dependency
     * @throws CommandException the command exception
     * @throws IOException Signals that an I/O exception has occurred.
     * @throws JDOMException the JDOM exception
     */
    ActionDependency checkPushMissingDependencies(CoordinatorActionBean coordAction,
            boolean registerForNotification) throws CommandException, IOException, JDOMException;

    /**
     * Gets the missing dependencies.
     *
     * @param coordAction the coord action
     * @return the missing dependencies
     * @throws CommandException the command exception
     * @throws IOException Signals that an I/O exception has occurred.
     * @throws JDOMException the JDOM exception
     */
    public Map<String, ActionDependency> getMissingDependencies(CoordinatorActionBean coordAction)
            throws CommandException, IOException, JDOMException;

    /**
     * Gets the first missing dependency.
     *
     * @return the first missing dependency
     */
    public String getFirstMissingDependency();

    /**
     * Check pull missing dependencies.
     *
     * @param coordAction the coord action
     * @param existList the exist list
     * @param nonExistList the non exist list
     * @return true, if successful
     * @throws IOException Signals that an I/O exception has occurred.
     * @throws JDOMException the JDOM exception
     */
    boolean checkPullMissingDependencies(CoordinatorActionBean coordAction, StringBuilder existList,
            StringBuilder nonExistList) throws IOException, JDOMException;

    /**
     * Checks if is change in dependency.
     *
     * @param nonExistList the non exist list
     * @param missingDependencies the missing dependencies
     * @param nonResolvedList the non resolved list
     * @param status the status
     * @return true, if is change in dependency
     */
    boolean isChangeInDependency(StringBuilder nonExistList, String missingDependencies,
            StringBuilder nonResolvedList, boolean status);

    /**
     * Check unresolved.
     *
     * @param coordAction the coord action
     * @param eAction the element for the action
     * @return true, if successful
     * @throws Exception the exception
     */
    boolean checkUnresolved(CoordinatorActionBean coordAction, Element eAction)
            throws Exception;

}


text:
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include "headers.h"

//-----------------------------------------------------------------------------
//  External functions (declared in funcs.h)
//-----------------------------------------------------------------------------
//  qualrout_init            (called by swmm_start)
//  qualrout_execute         (called by routing_execute)

//-----------------------------------------------------------------------------
//  Function declarations
//-----------------------------------------------------------------------------
static void  findLinkMassFlow(int i, double tStep);
static void  findNodeQual(int j);
static void  findLinkQual(int i, double tStep);
static void  findSFLinkQual(int i, double tStep);
static void  findStorageQual(int j, double tStep);
static void  updateHRT(int j, double v, double q, double tStep);
static double getReactedQual(int p, double c, double v1, double tStep);
static double getMixedQual(double c, double v1, double wIn, double qIn,
              double tStep);

//=============================================================================

void    qualrout_init()
//
//  Input:   none
//  Output:  none
//  Purpose: initializes water quality concentrations in all nodes and links.
//
{
    int     i, p, isWet;
    double  c;

    for (i = 0; i < Nobjects[NODE]; i++)
    {
	    isWet = ( Node[i].newDepth > FUDGE );
        for (p = 0; p < Nobjects[POLLUT]; p++)
        {
            c = 0.0;
            if ( isWet ) c = Pollut[p].initConcen;
            Node[i].oldQual[p] = c;
            Node[i].newQual[p] = c;
        }
    }

    for (i = 0; i < Nobjects[LINK]; i++)
    {
        isWet = ( Link[i].newDepth > FUDGE );
        for (p = 0; p < Nobjects[POLLUT]; p++)
        {
            c = 0.0;
            if ( isWet ) c = Pollut[p].initConcen;
            Link[i].oldQual[p] = c;
            Link[i].newQual[p] = c;
        }
    }
}

//=============================================================================

void qualrout_execute(double tStep)
//
//  Input:   tStep = routing time step (sec)
//  Output:  none
//  Purpose: routes water quality constituents through the drainage
//           network over the current time step.
//
{
    int    i, j;
    double qIn, vAvg;

    // --- find mass flow each link contributes to its downstream node
    for ( i = 0; i < Nobjects[LINK]; i++ ) findLinkMassFlow(i, tStep);

    // --- find new water quality concentration at each node
    for (j = 0; j < Nobjects[NODE]; j++)
    {
        // --- get node inflow and average volume
        qIn = Node[j].inflow;
        vAvg = (Node[j].oldVolume + Node[j].newVolume) / 2.0;

        // --- save inflow concentrations if treatment applied
        if ( Node[j].treatment )
        {
            if ( qIn < ZERO ) qIn = 0.0;
            treatmnt_setInflow(qIn, Node[j].newQual);
        }

        // --- find new quality at the node
        if ( Node[j].type == STORAGE || Node[j].oldVolume > FUDGE )
        {
            findStorageQual(j, tStep);
        }
        else findNodeQual(j);

        // --- apply treatment to new quality values
        if ( Node[j].treatment ) treatmnt_treat(j, qIn, vAvg, tStep);
    }

    // --- find new water quality in each link
    for ( i=0; i<Nobjects[LINK]; i++ ) findLinkQual(i, tStep);
}

//=============================================================================

double getMixedQual(double c, double v1, double wIn, double qIn, double tStep)
//
//  Input:   c = pollutant concentration at start of time step (mass/ft3)
//           v1 = volume at start of time step (ft3)
//           wIn = mass inflow rate (mass/sec)
//           qIn = flow inflow rate (cfs)
//           tStep = time step (sec)
//  Output:  returns pollutant concentration at end of time step (mass/ft3)
//  Purpose: finds pollutant concentration within a completely mixed volume
//
{
    double vIn, cIn, cMax;

    // --- compute concentration of any inflow
    if ( qIn <= ZERO ) return c;
    vIn = qIn * tStep;
    cIn = wIn * tStep / vIn;

    // --- find limit on final concentration
    cMax = MAX(c, cIn);

    // --- mix inflow with reacted contents
    c = (c*v1 + wIn*tStep) / (v1 + vIn);
    c = MIN(c, cMax);
    c = MAX(c, 0.0);
    return c;
}

//=============================================================================

void findLinkMassFlow(int i, double tStep)
//
//  Input:   i = link index
//           tStep = time step (sec)
//  Output:  none
//  Purpose: adds constituent mass flow out of link to the total
//           accumulation at the link's downstream node.
//
//  Note:    Node[].newQual[], the accumulator variable, already contains
//           contributions from runoff and other external inflows from
//           calculations made in routing_execute().
{
    int    j, p;
    double qLink, w;

    // --- find inflow to downstream node
    qLink = Link[i].newFlow;

    // --- identify index of downstream node
    j = Link[i].node2;
    if ( qLink < 0.0 ) j = Link[i].node1;
    qLink = fabs(qLink);

    // --- add mass inflow from link to total at downstream node
    for (p = 0; p < Nobjects[POLLUT]; p++)
    {
	    w = qLink * Link[i].oldQual[p];
        Node[j].newQual[p] += w;
	    Link[i].totalLoad[p] += w * tStep;
    }
}

//=============================================================================

void findNodeQual(int j)
//
//  Input:   j = node index
//  Output:  none
//  Purpose: finds new quality in a node with no storage volume.
//
{
    int    p;
    double qNode;

    // --- if there is flow into node then concen. = mass inflow/node flow
    qNode = Node[j].inflow;
    if ( qNode > ZERO )
    {
        for (p = 0; p < Nobjects[POLLUT]; p++)
        {
            Node[j].newQual[p] /= qNode;
        }
    }

    // --- otherwise concen. is 0
    else for (p = 0; p < Nobjects[POLLUT]; p++) Node[j].newQual[p] = 0.0;
}

//=============================================================================

void findLinkQual(int i, double tStep)
//
//  Input:   i = link index
//           tStep = routing time step (sec)
//  Output:  none
//  Purpose: finds new quality in a link at end of the current time step.
//
{
    int    j,                // upstream node index
           k,                // conduit index
           p;                // pollutant index
    double wIn,              // pollutant mass inflow rate (mass/sec)
           qIn,              // inflow rate (cfs)
           qOut,             // outflow rate (cfs)
           v1,               // link volume at start of time step (ft3)
           v2,               // link volume at end of time step (ft3)
           c1,               // current concentration within link (mass/ft3)
           c2;               // new concentration within link (mass/ft3)

    // --- identify index of upstream node
    j = Link[i].node1;
    if ( Link[i].newFlow < 0.0 ) j = Link[i].node2;

    // --- link concentration equals that of upstream node when
    //     link is not a conduit or is a dummy link
    if ( Link[i].type != CONDUIT || Link[i].xsect.type == DUMMY )
    {
        for (p = 0; p < Nobjects[POLLUT]; p++)
        {
            Link[i].newQual[p] = Node[j].newQual[p];
        }
        return;
    }

    // --- concentrations are zero in an empty conduit
    if ( Link[i].newDepth <= FUDGE )
    {
        for (p = 0; p < Nobjects[POLLUT]; p++)
        {
            Link[i].newQual[p] = 0.0;
        }
        return;
    }

    // --- Steady Flow routing requires special treatment
    if ( RouteModel == SF )
    {
        findSFLinkQual(i, tStep);
        return;
    }

    // --- get inlet & outlet flow
    k = Link[i].subIndex;
    qIn  = fabs(Conduit[k].q1) * (double)Conduit[k].barrels;
    qOut = fabs(Conduit[k].q2) * (double)Conduit[k].barrels;

    // --- get starting and ending volumes
    v1 = Link[i].oldVolume;
    v2 = Link[i].newVolume;

    // --- adjust inflow to compensate for volume change when inflow = outflow
    if (qIn == qOut)
    {
        qIn = qIn + (v2 - v1) / tStep;
        qIn = MAX(qIn, 0.0);
    }

    // --- for each pollutant
    for (p = 0; p < Nobjects[POLLUT]; p++)
    {
        // --- determine mass lost to first order decay
        c1 = Link[i].oldQual[p];
        c2 = getReactedQual(p, c1, v1, tStep);

        // --- mix inflow to conduit with previous contents
        wIn = Node[j].newQual[p]*qIn;
        c2 = getMixedQual(c2, v1, wIn, qIn, tStep);

        // --- assign new concen. to link
        Link[i].newQual[p] = c2;
    }
}

//=============================================================================

void  findSFLinkQual(int i, double tStep)
//
//  Input:   i = link index
//           tStep = routing time step (sec)
//  Output:  none
//  Purpose: finds new quality in a link at end of the current time step for
//           Steady Flow routing.
//
{
    int j = Link[i].node1;
    int p;
    double c1, c2;
    double lossRate;
    double t = tStep;

    // --- for each pollutant
    for (p = 0; p < Nobjects[POLLUT]; p++)
    {
        // --- conduit's quality equals upstream node quality
        c1 = Node[j].newQual[p];
        c2 = c1;

        // --- apply first-order decay over travel time
        if ( Pollut[p].kDecay > 0.0 )
        {
            c2 = c1 * exp(-Pollut[p].kDecay * t);
            c2 = MAX(0.0, c2);
            lossRate = (c1 - c2) * Link[i].newFlow;
            massbal_addReactedMass(p, lossRate);
        }
        Link[i].newQual[p] = c2;
    }
}

//=============================================================================

void  findStorageQual(int j, double tStep)
//
//  Input:   j = node index
//           tStep = routing time step (sec)
//  Output:  none
//  Purpose: finds new quality in a node with storage volume.
//
{
    int    p;                // pollutant index
    double qIn,              // inflow rate (cfs)
           wIn,              // pollutant mass inflow rate (mass)
           v1,               // volume at start of time step (ft3)
           c1,               // initial pollutant concentration (mass/ft3)
           c2;               // final pollutant concentration (mass/ft3)

    // --- get inflow rate & initial volume
    qIn = Node[j].inflow;
    v1 = Node[j].oldVolume;

    // --- update hydraulic residence time for storage nodes
    //     (HRT can be used in treatment functions)
    if ( Node[j].type == STORAGE )
    {
        updateHRT(j, Node[j].oldVolume, qIn, tStep);
    }

    // --- for each pollutant
    for (p = 0; p < Nobjects[POLLUT]; p++)
    {
        // --- get current concentration
        c1 = Node[j].oldQual[p];

        // --- apply first order decay only if no separate treatment function
        if ( Node[j].treatment == NULL ||
             Node[j].treatment[p].equation == NULL )
        {
            c1 = getReactedQual(p, c1, v1, tStep);
        }

        // --- mix inflow with current contents (mass inflow rate was
        //     temporarily saved in Node[j].newQual)
        wIn = Node[j].newQual[p];
        c2 = getMixedQual(c1, v1, wIn, qIn, tStep);

        // --- assign new concen. to node
        Node[j].newQual[p] = c2;
    }
}

//=============================================================================

void updateHRT(int j, double v, double q, double tStep)
//
//  Input:   j = node index
//           v = storage volume (ft3)
//           q = inflow rate (cfs)
//           tStep = time step (sec)
//  Output:  none
//  Purpose: updates hydraulic residence time (i.e., water age) at a
//           storage node.
//
{
    int    k = Node[j].subIndex;
    double hrt = Storage[k].hrt;
    if ( v < ZERO ) hrt = 0.0;
    else hrt = (hrt + tStep) * v / (v + q*tStep);
    Storage[k].hrt = MAX(hrt, 0.0);
}

//=============================================================================

double getReactedQual(int p, double c, double v1, double tStep)
//
//  Input:   p = pollutant index
//           c = initial concentration (mass/ft3)
//           v1 = initial volume (ft3)
//           tStep = time step (sec)
//  Output:  none
//  Purpose: applies a first order reaction to a pollutant over a given
//           time step.
//
{
    double c2, lossRate;
    double kDecay = Pollut[p].kDecay;

    if ( kDecay == 0.0 ) return c;
    c2 = c * (1.0 - kDecay * tStep);
    c2 = MAX(0.0, c2);
    lossRate = (c - c2) * v1 / tStep;
    massbal_addReactedMass(p, lossRate);
    return c2;
}

//=============================================================================


text:
from operator import itemgetter
import itertools
import json
import sys

states = {
        'AK': 'Alaska',
        'AL': 'Alabama',
        'AR': 'Arkansas',
        'AS': 'American Samoa',
        'AZ': 'Arizona',
        'CA': 'California',
        'CO': 'Colorado',
        'CT': 'Connecticut',
        'DC': 'District of Columbia',
        'DE': 'Delaware',
        'FL': 'Florida',
        'GA': 'Georgia',
        'GU': 'Guam',
        'HI': 'Hawaii',
        'IA': 'Iowa',
        'ID': 'Idaho',
        'IL': 'Illinois',
        'IN': 'Indiana',
        'KS': 'Kansas',
        'KY': 'Kentucky',
        'LA': 'Louisiana',
        'MA': 'Massachusetts',
        'MD': 'Maryland',
        'ME': 'Maine',
        'MI': 'Michigan',
        'MN': 'Minnesota',
        'MO': 'Missouri',
        'MP': 'Northern Mariana Islands',
        'MS': 'Mississippi',
        'MT': 'Montana',
        'NA': 'National',
        'NC': 'North Carolina',
        'ND': 'North Dakota',
        'NE': 'Nebraska',
        'NH': 'New Hampshire',
        'NJ': 'New Jersey',
        'NM': 'New Mexico',
        'NV': 'Nevada',
        'NY': 'New York',
        'OH': 'Ohio',
        'OK': 'Oklahoma',
        'OR': 'Oregon',
        'PA': 'Pennsylvania',
        'PR': 'Puerto Rico',
        'RI': 'Rhode Island',
        'SC': 'South Carolina',
        'SD': 'South Dakota',
        'TN': 'Tennessee',
        'TX': 'Texas',
        'UT': 'Utah',
        'VA': 'Virginia',
        'VI': 'Virgin Islands',
        'VT': 'Vermont',
        'WA': 'Washington',
        'WI': 'Wisconsin',
        'WV': 'West Virginia',
        'WY': 'Wyoming'
}

def print_averages(averages):
        for i in averages:
            print i[0] + ' ' + str(i[1])

def prints(items):
    i = 0
    for it in items:
        i = i + 1
        print it

def main():
    scores = extract_scores(sys.argv[1])
    tweets = extract_tweets(sys.argv[2])

    #tweets = tweets[0:10]

    sentiments = list(calculate_sentiments(tweets, scores))
    states = list(extract_states(sys.argv[2]))

    states_average = calculate_average(sentiments, states)
    states_average = sorted(states_average.items(), key = itemgetter(1), reverse=True)

    #print_averages(states_average[0:1])
    print states_average[0:1][0][0]
    #prints(states)
    #prints(tweets)
    #prints(sentiments)

def extract_scores(fp):
    sent_file = open(fp)
    scores = {}
    for line in sent_file:
        term, score = line.split("\t")
        scores[term] = int(score)
    return scores

def extract_tweets(fp):
   items = []
   with open(fp) as json_file:
       for line in json_file:
           tweet = json.loads(line)

           if ('text' in tweet and 'lang' in tweet and tweet.get('lang') == 'en'):
               text = tweet.get('text').replace('\n', '<br />')
               items.append(text)
   return items

def extract_states(fp):
   items = []
   with open(fp) as json_file:
       for line in json_file:
           tweet = json.loads(line)

           if not ('text' in tweet and 'lang' in tweet and tweet.get('lang') == 'en'):continue

           state = extract_state_from_place(tweet)
           if not state == None:
               items.append(state)
               continue
           state = extract_state_from_user(tweet)
           if not state == None:
               items.append(state)
               continue
           items.append('--')

   return items

def calculate_average(sentiments, states):
    i = 0
    calculations = {}
    for item in states:
        if item == '--': continue;

        if calculations.has_key(item) == False:
            calculations[item] = {'count': 0, 'sum': 0, 'average': 0}
        count = calculations[item]['count']
        tot = calculations[item]['sum']
        avg = calculations[item]['average']

        calculations[item]['average'] = float(tot + sentiments[i]) / float(count + 1)
        calculations[item]['sum'] = tot + sentiments[i]
        calculations[item]['count'] = count + 1

        i = i + 1

    averages = {}
    for item in calculations:
        averages[item] = calculations[item]['average']
    return averages

def extract_state_from_place(tweet):
    if not 'place' in tweet:
        return None

    place = tweet.get('place')

    if place == None or not 'full_name' in place: return None
    if not 'country_code' in place: return None
    if not 'place_type' in place: return None

    if not place.get('country_code') == "US":
        return None
    if not place.get('place_type') == "city":
        return None
    fullname = place.get('full_name')
    state = fullname[-2:]
    if states.has_key(state) == False: return None

    #print "%s - %s - %s - %s"% (state, fullname, states.has_key(state), states[state])
    return state

def extract_state_from_user(tweet):
    if not 'user' in tweet:
        return None

    user = tweet.get('user')

    if user == None or not 'location' in user: return None
    fullname = user.get('location')
    state = fullname[-2:]
    if states.has_key(state) == False: return None

    #print "%s - %s - %s - %s"% (state, fullname, states.has_key(state), states[state])
    return state

def calculate_sentiments(tweets, scores):
    for tweet in tweets:
        sentiment = 0
        for word in tweet.split(' '):
            if (scores.has_key(word.lower()) == True):
                sentiment = sentiment + int(scores.get(word.lower()))
        yield sentiment

if __name__ == '__main__':
    main()


text:
package stroom.widget.menu.client.presenter;

import stroom.widget.popup.client.event.HidePopupEvent;
import stroom.widget.popup.client.event.HidePopupRequestEvent;
import stroom.widget.popup.client.event.ShowPopupEvent;
import stroom.widget.popup.client.presenter.PopupPosition;

import com.google.gwt.dom.client.Element;
import com.google.gwt.event.shared.EventHandler;
import com.google.gwt.event.shared.GwtEvent;
import com.google.gwt.event.shared.HandlerRegistration;
import com.google.gwt.event.shared.HasHandlers;

import java.util.ArrayList;
import java.util.List;

public class ShowMenuEvent
        extends GwtEvent<ShowMenuEvent.Handler> {

    private static Type<Handler> TYPE;

    private final List<Item> items;
    private final PopupPosition popupPosition;
    private final ShowPopupEvent.Handler showHandler;
    private final HidePopupEvent.Handler hideHandler;
    private final Element[] autoHidePartners;

    private ShowMenuEvent(final List<Item> items,
                          final PopupPosition popupPosition,
                          final ShowPopupEvent.Handler showHandler,
                          final HidePopupEvent.Handler hideHandler,
                          final Element[] autoHidePartners) {
        this.items = items;
        this.popupPosition = popupPosition;
        this.autoHidePartners = autoHidePartners;
        this.showHandler = showHandler;
        this.hideHandler = hideHandler;
    }

    public static Builder builder() {
        return new Builder();
    }

    public static Type<Handler> getType() {
        if (TYPE == null) {
            TYPE = new Type<>();
        }
        return TYPE;
    }

    @Override
    public final Type<Handler> getAssociatedType() {
        return TYPE;
    }

    public List<Item> getItems() {
        return items;
    }

    public PopupPosition getPopupPosition() {
        return popupPosition;
    }

    public ShowPopupEvent.Handler getShowHandler() {
        return showHandler;
    }

    public HidePopupEvent.Handler getHideHandler() {
        return hideHandler;
    }

    public Element[] getAutoHidePartners() {
        return autoHidePartners;
    }

    @Override
    protected void dispatch(Handler handler) {
        handler.onShow(this);
    }

    public interface Handler extends EventHandler {

        void onShow(ShowMenuEvent event);
    }

    public interface HasShowMenuHandlers extends HasHandlers {

        HandlerRegistration addShowMenuHandler(ShowMenuEvent.Handler handler);
    }

    public static class Builder {

        private List<Item> items;
        private PopupPosition popupPosition;
        private ShowPopupEvent.Handler showHandler;
        private HidePopupEvent.Handler hideHandler;
        private final List<Element> autoHidePartners = new ArrayList<>();

        public Builder() {
        }

        public Builder items(final List<Item> items) {
            this.items = items;
            return this;
        }

        public Builder popupPosition(final PopupPosition popupPosition) {
            this.popupPosition = popupPosition;
            return this;
        }

        public Builder addAutoHidePartner(final Element... autoHidePartner) {
            if (autoHidePartner != null) {
                for (final Element element : autoHidePartner) {
                    this.autoHidePartners.add(element);
                }
            }
            return this;
        }

        public Builder onShow(final ShowPopupEvent.Handler handler) {
            this.showHandler = handler;
            return this;
        }

        public Builder onHide(final HidePopupEvent.Handler handler) {
            this.hideHandler = handler;
            return this;
        }

        public void fire(HasHandlers hasHandlers) {
            Element[] elements = null;
            if (autoHidePartners.size() > 0) {
                elements = autoHidePartners.toArray(new Element[0]);
            }

            hasHandlers.fireEvent(new ShowMenuEvent(
                    items,
                    popupPosition,
                    showHandler,
                    hideHandler,
                    elements));
        }
    }
}


text:
export default (fileInfo, api) => {
    const j = api.jscodeshift;

    return j(fileInfo.source)
        .find(j.CallExpression, {
            callee: {
                type: 'MemberExpression',
                property: {
                    type: 'Identifier',
                    name: 'log'
                },
            }
        })
        .remove()
        .toSource()
};


text:
Jexer Work Log
==============

October 26, 2018

For those watching Jexer on GitHub, some news.

First, Jexer's code repository will be moving to GitLab in the next
3-6 months.  Development is active again, but happening in a private
repo for now.

Second, a release will be coming sometime in 2019.  This will feature
LOTS of bug fixes, a couple more widgets, i18n/l10n ready, and will
also be published on Maven.  The GitLab repo will go public, and the
GitHub repo will be deleted.

Next year's release will also mark Jexer's transition from "alpha"
(the API can change at anytime, expect lots of breakage) to "beta"
(API will not change, mostly feature complete, need help from others
to find more defects).

I am currently unsure what I want to do with Jexer after the Maven
release.  My real life is drifting away from public open source as a
hobby.  (To be blunt, I still enjoy writing code, but I am finding
myself less interested in sharing it.)  I will likely keep the issue
tracker open to collect reports, but might not enable pull requests.
We will see how I feel when the time comes.

July 13, 2018

This project isn't dead, I swear!

My meatspace life has been very crazy busy.  But I do hope to be back
to work on this in another couple months.

December 15, 2017

We now have 24-bit RGB colors working with Swing backend.
EMCA48Terminal isn't happy though, let's try to fix that...  still no
dice.  So RGB is there for ECMA48 backend, but it sometimes flickers
or disappears.  I'm not sure yet where the fault lies.  Ah, found it!
Cell.isBlank() wasn't checking RGB.

Well, I do say it is rather pretty now.  Let's get this committed and
uploaded.

December 14, 2017

TComboBox is stubbed in, and it was quite simple: just a TField and
TList, and a teeny bit of glue.  Along the way I renamed TCheckbox to
TCheckBox, which was almost more work than TComboBox.  Heh.  Things
are starting to come together indeed.

TSpinner is in.  Now working on TCalendar...  ...and TCalendar is in!

December 13, 2017

A user noticed that the example code given in the README.md caused the
main window to freeze when clicking close.  Turns out that was due to
the addWindow(new TWindow(...)) line, which led to TWindow appearing
in TApplication's window list twice.  Fixed the README, and then made
TApplication.addWindow a package private function plus a check to
ensure it isn't added twice.

On the home front, my main box is now a Fedora 26 running Plasma
desktop.  That ate a few weekends getting used to.  Current-era Linux
is pretty nice, systemd so far (cross fingers) isn't creating any real
problems, audio and wifi worked out of the box (thanks to Intel
chipsets), and I can finally have all of my books and references on
the same box as dev.  So woohoo!

SwingTerminal is getting the insets wrong, which is a bit aggravating.
So let's add adjustable insets in SwingComponent with a default
2-pixel border around the whole thing, which I can tweak for my
laptop.  Done!

Alright, so where are we?  Well, I will have some time in the evenings
over the next couple weeks to put into projects.  This one will get a
little bit of love, probably a new widget or two; Qodem might get
libssh2 + mdebtls support in Windows if those aren't too involved;
Jermit will get a little more push towards a Kermit implementation.

October 17, 2017

I finally gave up the ghost on using gcj as the default compiler due
to its awesome unused imports messages, and learned how to get PMD to
do that job.  Which promptly created 1000+ warning messages related to
class item order (variables, constructors, methods), nested ifs,
useless checks, and so on.  So now we go on a code sweep to fix those,
and along the way set a new class template.  Since this is so large
and invasive, I will bite the bullet now and get it done before the
next release which will get it out on Maven finally.

August 16, 2017

Holy balls this has gotten so much faster!  It is FINALLY visibly
identical in speed to the original d-tui: on xterm it is glass
smooth.  CPU load is about +/- 10%, idling around 5%.

I had to dramatically rework the event processing order, but now it
makes much more sense.  TApplication.run()'s sole job is to listen for
backend I/O, push it into drainEventQueue, and wake up the consumer
thread.  The consumer thread's run() has the job of dealing with the
event, AND THEN calling doIdles and updating the screen.  That was the
big breakthrough: why bother having main thread do screen updates?  It
just leads to contention everywhere as it tries to tell the consumer
thread to lay off its data structures, when in reality the consumer
thread should have been the real owner of those structures in the
first place!  This was mainly an artifact of the d-tui fiber threading
design.

So now we have nice flow of events:

* I/O enters the backend, backend wakes up main thread.

* Main thread grabs events, wakes up consumer thread.

* Consumer thread does work, updates screen.

* Anyone can call doRepaint() to get a screen update shortly
  thereafter.

* Same flow for TTerminalWindow: ECMA48 gets remote I/O, calls back
  into TTerminalWindow, which then calls doRepaint().  So in this case
  we have a completely external thread asking for a screen update, and
  it is working.

Along the way I also eliminated the Screen.dirty flag and cut out
calls to CellAttribute checks.  Overall we now have about 80% less CPU
being burned and way less latency.  Both HPROF samples and times puts
my code at roughly 5% of the total, all the rest is the
sleeping/locking infrastructure.

August 15, 2017

I cut 0.0.5 just now, and also applied for a Sonatype repository.
It was a reasonable spot: TEditor was working albeit buggy, and a bug
had just come in on the main TApplication run loop.  So we are about
to embark upon some performance work again, it's been probably version
0.0.2 or so since the last cycle.

Code size: 40446 lines.

Now switching head to 0.0.6 and taking a small break.

August 14, 2017

TEditor is basically done.  Mouse movement, keyboard movement,
backspace / delete / enter / etc. are all in.  Things are starting to
look pretty good.

I'm going to prep for a final cut and release tag tomorrow or the next
evening.  I need to take a break and get some meatspace life dealt
with.

August 12, 2017

TEditor is stubbed in about 50% complete now.  I have a Highlighter
class that provides different colors based on Word text values, but it
is a lot too simple to do true syntax highlighting.  I am noodling on
the right design that would let TEditor be both a programmer's editor
(so Highlighter needs to have state and do a lexical scan) and a word
processor (where Word needs to tokenize on whitespace).  I estimate
probably a good 2-4 weeks left to get the editor behavior where I want
it, and then after that will be the 0.0.5 release.

Finding more minor paper cuts and fixing them: the mouse cursor being
ahead of a window drag event, SwingTerminal resetting blink on new
input, prevent TWindow from resizing down into the status bar.

August 8, 2017

Multiscreen is looking really cool!  Demo6 now brings up three
screens, including one that is inside a TWindow of a different
application.

August 7, 2017

Had trouble sleeping, what with a bunch of imaginative thoughts for
this release.  jexer.backend will be the ultimate destination for
jexer.session and most of jexer.io.  TerminalReader will be the
interface for keyboard and mouse events.  cmScreenConnected and
cmScreenDisconnected will be new events to represent a screen
appearing/disappearing, and MultiBackend will be a new backend
multiplexer that goes full XRandR.  Several new demos demonstrating
multi-screen support will be coming along.

August 6, 2017

Time to clean up more API, particularly between Backend and Screen.
Both of these will be interfaces soon, so that one could easily
subclass JComponent and implement both Screen and Backend.  The
original code evolved out of Qodem, where screen.c and input.c were
two different things leading to ECMA48Screen and ECMA48Terminal, but
now there is really no need to keep them separate.  It also
complicates the constructors, as these are basically friend classes
that have used package private access to get around their artificial
separation.

When I get this done it should be a lot easier to do any of:

* Pass a JFrame or JComponent to SwingBackend and have it add itself,
  like any other Swing widget.

* Construct a SwingBackend and add it to any regular JComponent.

* Have multiple TApplications running inside the same Swing
  application, including having actions affect each other.  (Will also
  need to ensure that TWidgets/TWindows are not in different
  TApplication collections.)

* Build a Backend/Screen multiplexer, so that one could have a ECMA48
  TApplication listening on a port and a local Swing monitor for it.

* Build a Backend/Screen manager, so that one could have multiple
  ECMA48 screens acting as a single large screen (e.g. XRandR).

Now I need to decide which package will collect Backend, SessionInfo,
and Screen.  jexer.io has some java.io stuff, so it stays anyway.

July 28, 2017

Got very busy with my meatspace life, now getting a chance to come
back around.

I gave up on TEditor knowing about graphemes, instead pulling back to
simple Cells.  This will be better anyway in the long run, as getting
grapheme support in Screen someday will also get it for me in TEditor
for free.  But it does mean that TEditor will chew through much more
RAM than it needs to for a text file.  Performance optimization will
come someday.  But this means I can also go back to gcj, because I
really like its warnings about unused imports.

I've got a POM stubbed in, and created an account over at sonatype.
If it isn't too hard, I will try to get 0.0.5 released into the maven
universe.  But that is still a bit away, I need TEditor running with
syntax highlighting first.

July 17, 2017

Focus-follows-mouse is in, as is NOCLOSEBOX.

July 15, 2017

I think I have cleaned up most of the window show/hide/activate mess
in TApplication.  Demo4 has some cool interactions between a
background TDesktop and several foreground TWindows, which helped
expose bugs.

July 9, 2017

While working on TWindow.hide/show I decided that I am sick of
TApplication's active window handling.  TApplication makes lots of
assumptions, things are too fragile between modal and not, and one
cannot easily say window.activate().  So I will also be changing that
too. ... Code is still a bit of a mess, but hooks are in place at
least for show/hide/activate.

July 8, 2017

Qodem 1.0.0 released last month, I had a vacation, and a Jexer user
(nikiroo) started opening up pull requests. :-) So back unto the
breach we go!

TButton is now animated so that there is some feedback when selected
via keyboard.  StringJustifier was written which permits TText's to
have left/centered/right and full justification.  TDesktop is now in
too which can act as a permanent max-sized window without borders.

Next up is Viewport, an interface to collect scrollbar API, and then a
cleaner API for scrollable widgets and windows.  After that is more
window API: hide/show/maximize/restore, and unclosable windows.  I am
cherry-picking bits from @nikiroo's PRs, which will likely break them
before it fixes things, but I will find some way to get Niki credited
with those pieces.

March 21, 2017

I am starting to gear up for making Jexer a serious project now.  I've
created its SourceForge project, linked it back to GitHub, have most
of its web page set up (looks like Qodem's), and released 0.0.4.  And
then this morning saw an out-of-bounds exception if you kill the main
demo window.  Glad I marked it Alpha on SourceForge...

Yesterday I was digging around the other Turbo Vision derived projects
while populating the about page, and made a sad/happy-ish realization:
Embarcadero could probably get all of them shut down if it really
wanted to, including Free Vision.  I uncovered some hidden history in
Free Vision, such that it appears that Graphics Vision had some
licensed Borland code in it, so there might be enough mud in the air
that Free Vision could be shut down the same way RHTVision was.  But
even worse is the SCOTUS ruling on Oracle vs Google: if APIs are
copyrighted (regardless of their thoughts on fair use), then any
software that matches the API of a proprietary project might find
itself subject to an infringement case.  So that too could shut down
the other API-compatible TV clones.

Fortunately, Jexer (and D-TUI) is completely new, and has no API
compatibility with Turbo Vision.  Jexer could be a new root to a whole
generation of TUI applications.

March 18, 2017

TStatusBar is working, as is "smart" window placement.  Overall this
is looking quite nice.  Found a lot of other small paper cut items and
fixed them.  It looks absolutely gorgeous on Mac now.

Tomorrow I will get to the public wifi and get this uploaded.

Time to call this 0.0.4 now though.  We are up to 32,123 lines of
code.

March 17, 2017

Jexer is coming back to active development status.  I had a lot of
other projects ahead of it in the queue, mostly Qodem but also Jermit
and of course lots of actual day job work keeping me too tired for
afterhours stuff.  But here we are now, and I want to get Jexer to its
1.0.0 release before the end of 2018.  After that it will be a
critical bit of function for IWP and NIB, if I ever get those going.
I need to re-organize the demo app a bit so that it fits within 80x25,
and then get to TStatusBar.

A status bar will be an optional part of TWindow.  If it exists, then
it will be drawn last by TApplication and get events routed to it from
TWindow's event handlers.  This will have the nice effect that the
status bar can change depending on which window is active, without any
real extra work on TApplication's part.

Putting together a proper TODO now, with release and regression
checklists.  I think I will see if jexer is available at SourceForge,
and if so grab it.  Perhaps I can put together some good Turbo Vision
resources too.  At the very least direct people to the Borland-derived
C++ releases and Free Vision.


text:
package SimpleBot::Plugin::Topic;

##
# Topic.pm
# SimpleBot Plugin
# Based on an original design by TMFKsoft and also used in the popular Techie-Bot,
# these commands allow you set the room topic in pieces, so you can maintain
# an overall persistent topic with several "columns" that you can set individually.
#
# !topic Minecraft Chat
# !towner Eric
# !tverb is
# !tstatus Away
# !tdivider |
# !tstatic http://mywebsite.com
# !ton
# !toff
# !trefresh
# !treset
##

use strict;
use base qw( SimpleBot::Plugin );
use Time::HiRes qw/time/;
use Tools;

sub init {
	my $self = shift;
	$self->register_commands('topic', 'towner', 'tverb', 'tstatus', 'tdivider', 'tstatic', 'trefresh', 'treset', 'ton', 'toff');
}

sub handler {
	# generic handler for all our sub-commands
	# topic|towner|tverb|tstatus|tdivider|tstatic|trefresh|treset|ton|toff
	my ($self, $cmd, $value, $args) = @_;
	my $username = lc($args->{who});
	my $chan = nch($args->{channel});

	$self->{data}->{channels} ||= {};
	my $topic = $self->{data}->{channels}->{ sch($chan) } ||= {};

	$topic->{topic} ||= $chan;
	$topic->{towner} ||= $username;
	$topic->{tverb} ||= 'is';
	$topic->{tstatus} ||= 'Online';
	$topic->{tdivider} ||= '|';
	$topic->{tstatic} ||= 'http://'.$self->{params}->{server};

	if ($cmd =~ /(topic|towner|tverb|tstatus|tdivider|tstatic)/) {
		$topic->{$cmd} = trim($value);
	}
	elsif ($cmd eq 'treset') {
		$self->irc_cmd( 'topic', nch($chan), ' ' );
		return "Topic for $chan has been cleared.";
	}
	elsif ($cmd eq 'ton') { $topic->{tstatus} = 'Online'; }
	elsif ($cmd eq 'toff') { $topic->{tstatus} = 'Offline'; }

	my $sep = ' ' . $topic->{tdivider} . ' ';
	my $topic_line = join( $sep,
		$topic->{topic},
		$topic->{towner} . ' ' . $topic->{tverb} . ' ' . $topic->{tstatus},
		$topic->{tstatic}
	);

	$self->irc_cmd( 'topic', nch($chan), $topic_line );
	$self->dirty(1);

	# return "Topic for $chan has been updated.";
	return undef;
}

1;


text:
package com.varra.util;

import java.util.TimerTask;

import org.apache.log4j.Logger;

import com.varra.exception.ComputingException;

/**
 * A task that is executed at a specified time.
 *

 * Subclasses implement the Periodicity, if needed. Two TimerTasks are compared
 * with their name and next execution time.
 *
 * Note:  Use {@link #compute()}  method to write the business logic.
 *
 * @author Rajakrishna V.
 *         Reddy
 * @version 1.0
 */
public abstract class EnhancedTimerTask implements Comparable<EnhancedTimerTask>, Runnable, Computable
{

	/** The Constant ALWAYS_TRUE. */
	private static final int ALWAYS_TRUE = 1;

	/** The next execution time. */
	private transient long nextExecutionTime;

	/** The prev execution time. */
	private transient long prevExecutionTime = 0;

	/** If or not this is finished. */
	private transient boolean isFinished;

	/** If or not this is canceled. */
	private transient boolean isCanceled;

	/** If or not this is expired. */
	private transient boolean isExpired;

	/** The period. */
	private transient long period;

	/** If or not this is periodic. */
	private transient boolean isPeriodic;

	/** The event. */
	private Object event;

	/** The no of times, By default It is 1. */
	private int noOfTimes = 1;

	/** The no of exec times. */
	private transient int noOfExecTimes = 0;

	/** If or not this should be executed immediately. */
	private transient boolean executeImmediately;

	/** If or not this is . */
	private transient boolean isDaemon;

	/** The name. */
	private final String name;

	/** The running. */
	private transient boolean running;

	/** The single executable. */
	private transient boolean singleExecutable;

	/**
	 * Constructor for subclasses.
	 *
	 */
	public EnhancedTimerTask()
	{
		super();

		this.name = this.getClass().getName();
	}

	/**
	 * Constructor for subclasses.
	 *
	 * @param name
	 *            the name
	 */
	public EnhancedTimerTask(final String name)
	{
		super();

		this.name = name;
	}

	/* (non-Javadoc)
	 * @see com.varra.util.Computable#compute()
	 */
	@Override
	public abstract void compute() throws ComputingException;

	/**
	 * This method implementing is not advised, consider implementing {@link #compute()} method.
	 */
	public void run()
	{
		try
		{
			this.setRunning(true);
			compute();
			this.setRunning(false);
		}
		catch (Exception e)
		{
			Logger.getLogger(getClass().getName()).error("Error in executing: " + getName(), e);
		}
	}

	/**
	 * Sets the periodic.
	 *
	 * @param isPeriodic
	 *            the isPeriodic to set
	 */
	public void setPeriodic(final boolean isPeriodic)
	{
		this.isPeriodic = isPeriodic;
	}

	/**
	 * Returns whether this task is periodic. By default return false.
	 *
	 * @return true, if is periodic
	 * @see #getPeriod
	 */
	public boolean isPeriodic()
	{
		return isPeriodic;
	}

	/**
	 * Sets the period.
	 *
	 * @param period
	 *            the period to set
	 */
	public void setPeriod(final long period)
	{
		this.period = period;
		this.isPeriodic = true;
	}

	/**
	 * Returns the period of this task. By default returns 0.
	 *
	 * @return the period
	 * @see #isPeriodic
	 */
	public long getPeriod()
	{
		return period;
	}

	/**
	 * Returns the next time at which the task will be executed, ie the.
	 *
	 * @return the next execution time {@link #run} method is called.
	 * @see #updateNextExecutionTime
	 */
	public long getNextExecutionTime()
	{
		return nextExecutionTime;
	}

	/**
	 * Sets the next execution time.
	 *
	 * @see #getNextExecutionTime
	 */
	public void updateNextExecutionTime()
	{
		this.nextExecutionTime = System.currentTimeMillis() + getPeriod();
	}

	/**
	 * Marks this task as finished or not. When a task is finished, its
	 *
	 * @param value
	 *            the new finished {@link #run} method will not be called
	 *            anymore.
	 * @see #isFinished
	 */
	protected void setFinished(final boolean value)
	{
		isFinished = value;
	}

	/**
	 * Returns whether this task is finished.
	 *
	 * @return true, if is finished
	 * @see #setFinished
	 */
	public boolean isFinished()
	{
		return isFinished;
	}

	/**
	 * Checks if is expired.
	 *
	 * @return the isExpired
	 */
	public boolean isExpired()
	{
		return isExpired;
	}

	/**
	 * Sets the expired.
	 *
	 * @param isExpired
	 *            the isExpired to set
	 */
	public void setExpired(boolean isExpired)
	{
		this.isExpired = isExpired;
	}

	/**
	 * Returns whether this task is canceled.
	 *
	 * @return true, if is canceled
	 * @see #cancel
	 */
	public boolean isCanceled()
	{
		return isCanceled;
	}

	/**
	 * Cancels this {@link TimerTask}.
	 */
	public void cancel()
	{
		isCanceled = true;
	}

	/**
	 * Compares 2 TimeTasks by comparing their next execution times.
	 *
	 * @param other
	 *            the other
	 * @return the int
	 * @see #getNextExecutionTime
	 */
	public int compareTo(final EnhancedTimerTask other)
	{
		int returnValue = 0;
		if (other == null)
		{
			returnValue = 1;
		}
		else if (other == this)
		{
			returnValue = 0;
		}
		else
		{
			if (getNextExecutionTime() > other.getNextExecutionTime())
			{
				returnValue = 1;
			}
			else if (getNextExecutionTime() < other.getNextExecutionTime())
			{
				returnValue = -1;
			}
		}
		return returnValue;
	}

	/**
	 * Sets the event.
	 *
	 * @param event
	 *            the event to set
	 */
	public void setEvent(final Object event)
	{
		this.event = event;
	}

	/**
	 * Gets the event.
	 *
	 * @return the event
	 */
	public Object getEvent()
	{
		return event;
	}

	/**
	 * Sets the prev execution time.
	 *
	 * @param prevExecutionTime
	 *            the prevExecutionTime to set
	 */
	public void setPrevExecutionTime(final long prevExecutionTime)
	{
		this.prevExecutionTime = prevExecutionTime;
	}

	/**
	 * Gets the prev execution time.
	 *
	 * @return the prevExecutionTime
	 */
	public long getPrevExecutionTime()
	{
		return prevExecutionTime;
	}

	/**
	 * Sets the no of times to execute this particular task.
	 *
	 * @param noOfTimes
	 *            the noOfTimes to set
	 */
	public void setNoOfTimes(final int noOfTimes)
	{
		this.noOfTimes = noOfTimes;
	}

	/**
	 * Gets the no of times that executes this particular task.
	 *
	 * @return the noOfTimes
	 */
	public int getNoOfTimes()
	{
		return noOfTimes;
	}

	/**
	 * Gets the no of pending times.
	 *
	 * @return the noOfPendingTimes
	 */
	public int getNoOfPendingTimes()
	{
		if (isDaemon())
		{
			return ALWAYS_TRUE;
		}
		return noOfTimes - noOfExecTimes;
	}

	/**
	 * Increments the no of exec times.
	 */
	public void incrementNoOfExecTimes()
	{
		this.noOfExecTimes++;
	}

	/**
	 * Gets the no of exec times.
	 *
	 * @return the noOfExecTimes
	 */
	public int getNoOfExecTimes()
	{
		return noOfExecTimes;
	}

	/**
	 * Gets the elapsed time.
	 *
	 * @return the elapsedTime
	 */
	public long getElapsedTime()
	{
		if (prevExecutionTime == 0)
		{
			return 0;
		}
		return System.currentTimeMillis() - prevExecutionTime;
	}

	/**
	 * Checks if is daemon.
	 *
	 * @return the isDaemon
	 */
	public boolean isDaemon()
	{
		return isDaemon;
	}

	/**
	 * Sets the daemon.
	 *
	 * @param isDaemon
	 *            the isDaemon to set
	 */
	public void setDaemon(boolean isDaemon)
	{
		this.isDaemon = isDaemon;
	}

	/**
	 * Gets the name.
	 *
	 * @return the name
	 */
	public String getName()
	{
		return name;
	}

	/**
	 * Sets this flag if something happens and needs to execute this immediately
	 * rather waiting till the interval expires.

	 * Mostly useful in RealTime event processing, signal this flag when a event
	 * received.

	 * Note:  This doesn't guarantee the immediate execution, rather its just
	 * like requesting, {@link GlobalThread} will run this as soon as it has
	 * free worker threads.
	 *
	 * @param executeImmediately
	 *            the executeImmediately to set
	 * @see #isExecuteImmediatelySet()
	 * @since 3.0
	 */
	public void setExecuteImmediately(boolean executeImmediately)
	{
		this.executeImmediately = executeImmediately;
	}

	/**
	 * Checks if this is set for immediate execution.
	 *
	 * @return the executeImmediately
	 * @since 3.0
	 */
	public boolean isExecuteImmediatelySet()
	{
		return executeImmediately;
	}

	/**
	 * Sets this flag to avoid duplicate execution of this in case of the
	 * primary execution takes much time than expected.
	 *
	 * @param running
	 *            the running to set
	 * @see #isRunning()
	 * @since 3.0
	 */
	public void setRunning(boolean running)
	{
		if (singleExecutable)
		{
			this.running = running;
		}
	}

	/**
	 * Checks if this is already in execution.
	 *
	 * @return the running
	 * @since 3.0
	 */
	public boolean isRunning()
	{
		return running;
	}

	/**
	 * Sets the single executable to enable single execution of this instance at
	 * any time.
	 *
	 * Note:  If one instance is being executed by any thread, any other
	 * thread can't execute this.
	 *
	 * @param singleExecutable
	 *            the singleExecutable to set
	 */
	public void setSingleExecutable(boolean singleExecutable)
	{
		this.singleExecutable = singleExecutable;
	}

	/**
	 * Checks if is single executable.
	 *
	 * @return the singleExecutable
	 */
	public boolean isSingleExecutable()
	{
		return singleExecutable;
	}

	/*
	 * (non-Javadoc)
	 *
	 * @see java.lang.Object#toString()
	 */
	@Override
	public String toString()
	{
		final StringBuilder builder = new StringBuilder();
		builder.append("EnhancedTimerTask [name=");
		builder.append(name);
		builder.append(", isDaemon=");
		builder.append(isDaemon);
		if (isPeriodic)
		{
			builder.append(", isPeriodic=");
			builder.append(isPeriodic);
			builder.append(", period=");
			builder.append(period);
		}
		builder.append(", Total noOfTimes=");
		builder.append(noOfTimes);
		builder.append(", noOfExecTimes=");
		builder.append(noOfExecTimes);
		builder.append(", nextExecutionTime=");
		builder.append(nextExecutionTime);
		builder.append(", prevExecutionTime=");
		builder.append(prevExecutionTime);
		builder.append(", isFinished=");
		builder.append(isFinished);
		builder.append(", singleExecutable=");
		builder.append(singleExecutable);
		builder.append(", running=");
		builder.append(running);
		builder.append(", isCanceled=");
		builder.append(isCanceled);
		builder.append(", event=");
		builder.append(event);
		builder.append("]");
		return builder.toString();
	}
}


text:
package asw.efood.restaurantservice.web;

import asw.efood.restaurantservice.domain.*;

import org.springframework.stereotype.Controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.ui.Model;

import javax.servlet.http.HttpServletRequest;

import java.util.*;

import java.util.logging.Logger;

@Controller
@RequestMapping(path="/web")
public class RestaurantWebController {

	@Autowired
	private RestaurantService restaurantService;

    private final Logger logger = Logger.getLogger(this.getClass().toString());

	/* Trova il ristorante con restaurantId. */
	@GetMapping("/restaurants/{restaurantId}")
	public String getRestaurant(Model model, @PathVariable Long restaurantId) {
		logger.info("WEB CALL: getRestaurant " + restaurantId);
		Restaurant restaurant = restaurantService.getRestaurant(restaurantId);
		model.addAttribute("restaurant", restaurant);
		return "get-restaurant";
	}

	/* Trova il menu del ristorante con restaurantId. */
	@GetMapping("/restaurants/{restaurantId}/menu")
	public String getRestaurantMenu(Model model, @PathVariable Long restaurantId) {
		logger.info("WEB CALL: getRestaurantMenu " + restaurantId);
		Restaurant restaurant = restaurantService.getRestaurant(restaurantId);
		RestaurantMenu menu = restaurantService.getRestaurantMenu(restaurantId);
		model.addAttribute("restaurant", restaurant);
		model.addAttribute("menu", menu);
		return "get-restaurant-menu";
	}

	/* Trova tutti i ristoranti. */
	@GetMapping("/restaurants")
	public String getRestaurants(Model model) {
		logger.info("WEB CALL: getAllRestaurants");
		Collection<Restaurant> restaurants = restaurantService.getAllRestaurants();
		model.addAttribute("restaurants", restaurants);
		return "get-restaurants";
	}

	/* Crea un nuovo ristorante (form). */
	@GetMapping(value="/restaurants", params={"add"})
	public String getAddRestaurantForm(Model model) {
		model.addAttribute("form", new AddRestaurantForm());
		return "add-restaurant-form";
	}

	/* Crea un nuovo ristorante. */
	@PostMapping("/restaurants")
	public String addRestaurant(Model model, @ModelAttribute("form") AddRestaurantForm form) {
		String name = form.getName();
		String location = form.getLocation();
		logger.info("WEB CALL: createRestaurant " + name + ", " + location);
		Restaurant restaurant = restaurantService.createRestaurant(name, location);
		model.addAttribute("restaurant", restaurant);
		return "get-restaurant";
	}

	/* Crea o modifica il menu di un ristorante (form). */
	@PostMapping(value="/restaurants/{restaurantId}/menu", params={"edit"})
	public String getEditRestaurantMenuForm(Model model, @PathVariable Long restaurantId) {
		Restaurant restaurant = restaurantService.getRestaurant(restaurantId);
		RestaurantMenu menu = restaurantService.getRestaurantMenu(restaurantId);
		List<MenuItem> menuItems = menu.getMenuItems();
		if (menuItems==null) {
			menuItems = new ArrayList<>();
			// menu.setMenuItems(menuItems);
		}
		model.addAttribute("restaurant", restaurant);
		model.addAttribute("form", new EditRestaurantMenuForm(menuItems));
		return "edit-restaurant-menu-form";
	}

	/* Crea o modifica il menu di un ristorante. */
	@PostMapping("/restaurants/{restaurantId}/menu")
	public String createOrUpdateRestaurantMenu(Model model, @PathVariable Long restaurantId, @ModelAttribute("form") EditRestaurantMenuForm form) {
		logger.info("WEB CALL: createOrUpdateRestaurantMenu " + restaurantId);
		List<MenuItem> menuItems = form.getMenuItems();
		if (menuItems==null) {
			menuItems = new ArrayList<>();
		}
		Restaurant restaurant = restaurantService.createOrUpdateRestaurantMenu(restaurantId, menuItems);
		model.addAttribute("restaurant", restaurant);
		return "get-restaurant";
	}

	/* Crea o modifica il menu di un ristorante (form). */
	@PostMapping(value="/restaurants/{restaurantId}/menu", params={"addMenuItem"})
	public String addMenuItem(Model model, @PathVariable Long restaurantId, @ModelAttribute("form") EditRestaurantMenuForm form) {
		List<MenuItem> menuItems = form.getMenuItems();
		if (menuItems==null) {
			menuItems = new ArrayList<>();
			form.setMenuItems(menuItems);
		}
		menuItems.add(new MenuItem());
		Restaurant restaurant = restaurantService.getRestaurant(restaurantId);
		model.addAttribute("restaurant", restaurant);
		model.addAttribute("form", form);
		return "edit-restaurant-menu-form";
	}

	/* Crea o modifica il menu di un ristorante (form). */
	@PostMapping(value="/restaurants/{restaurantId}/menu", params={"removeMenuItem"})
	public String removeMenuItem(Model model, @PathVariable Long restaurantId,
				@ModelAttribute("form") EditRestaurantMenuForm form, HttpServletRequest req) {
		int index = Integer.valueOf(req.getParameter("removeMenuItem")).intValue();
		form.getMenuItems().remove(index);
		Restaurant restaurant = restaurantService.getRestaurant(restaurantId);
		model.addAttribute("restaurant", restaurant);
		model.addAttribute("form", form);
		return "edit-restaurant-menu-form";
	}

}