package main import ( "context" "fmt" "net/http" "os" //"log" "strconv" "sync" "github.com/gotk3/gotk3/glib" "github.com/gotk3/gotk3/gtk" "github.com/gotk3/gotk3/pango" ) var ( DEFAULT_PORT, DEFAULT_DIR, APP_VERSION string = "1313", ".", "v0.3.0" MAIN_WINDOW *gtk.ApplicationWindow ) func serve(port, dir string, label *gtk.Label) (*http.Server, *sync.WaitGroup) { if port == "" { port = DEFAULT_PORT } wg := &sync.WaitGroup{} mux := http.NewServeMux() mux.Handle("/", http.FileServer(http.Dir(dir))) server := &http.Server{Addr: ":" + port, Handler: mux} go func() { wg.Add(1) defer wg.Done() if err := server.ListenAndServe(); err != http.ErrServerClosed { errMsg := err.Error() if errMsg == "listen tcp :" + port + ": bind: address already in use" { errMsg = "Port " + port + " is already in use. Please use another port." } errDialog := gtk.MessageDialogNew(MAIN_WINDOW, gtk.DIALOG_DESTROY_WITH_PARENT, gtk.MESSAGE_ERROR, gtk.BUTTONS_CLOSE, "%s", errMsg) errDialog.SetDefaultSize(400, 100) errDialog.SetResizable(false) errDialog.Connect("response", func() { errDialog.Destroy() }) errDialog.Show() label.SetMarkup(fmt.Sprintf("Server shut down due to unexpected error.\n%s", errMsg)) } }() return server, wg } func main() { app, _ := gtk.ApplicationNew("net.mikunonaka.gofileserver", glib.APPLICATION_FLAGS_NONE) app.Connect("activate", func() { onActivate(app) }) app.Run(os.Args) } func onActivate(app *gtk.Application) { MAIN_WINDOW, _ = gtk.ApplicationWindowNew(app) MAIN_WINDOW.SetTitle("HTTP File Server") MAIN_WINDOW.SetDefaultSize(330, 230) MAIN_WINDOW.SetResizable(false) MAIN_WINDOW.SetPosition(gtk.WIN_POS_CENTER) box, _ := gtk.BoxNew(gtk.ORIENTATION_VERTICAL, 0) dirBox, _ := gtk.BoxNew(gtk.ORIENTATION_HORIZONTAL, 0) dirLabel, _ := gtk.LabelNew("Directory to serve: ") dirBox.PackStart(dirLabel, false, false, 5) browseButton, _ := gtk.FileChooserButtonNew("Browse Directory To Serve", gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER) dirBox.PackEnd(browseButton, false, false, 5) box.PackStart(dirBox, false, false, 5) portBox, _ := gtk.BoxNew(gtk.ORIENTATION_HORIZONTAL, 0) portLabel, _ := gtk.LabelNew("Port:") portBox.PackStart(portLabel, false, false, 5) portInput, _ := gtk.EntryNew() portInput.SetText(DEFAULT_PORT) portBox.PackEnd(portInput, false, false, 5) box.PackStart(portBox, false, false, 5) buttonBox, _ := gtk.BoxNew(gtk.ORIENTATION_HORIZONTAL, 0) aboutButton, _ := gtk.ButtonNewWithLabel("About") buttonBox.PackStart(aboutButton, false, true, 5) buttonSwitch, _ := gtk.ButtonNewWithLabel("Start") buttonBox.PackEnd(buttonSwitch, true, true, 5) box.PackStart(buttonBox, false, false, 5) statusLabel, _ := gtk.LabelNew("") statusLabel.SetLineWrap(true) statusLabel.SetLineWrapMode(pango.WrapMode(gtk.ALIGN_START)) statusLabel.SetJustify(gtk.JUSTIFY_CENTER) box.PackStart(statusLabel, false, true, 10) var ( server *http.Server wg *sync.WaitGroup on = false ) buttonSwitch.Connect("clicked", func() { // clicking the button too fast too many times crashes the app // this disables the button as long as the button is running // user likely won't notice the button greying out or something buttonSwitch.SetSensitive(false) defer buttonSwitch.SetSensitive(true) port, _ := portInput.GetText() dir := browseButton.GetFilename() if dir == "" { dir = DEFAULT_DIR } if on { server.Shutdown(context.TODO()) statusLabel.SetMarkup("Server was terminated by user.") } else { go func() { server, wg = serve(port, dir, statusLabel) //killServerDone.Add(1) // do this after server starts on = true buttonSwitch.SetLabel("Stop") statusLabel.SetMarkup(fmt.Sprintf("Serving\n%s\nOn http://localhost:%s", dir, port, port)) browseButton.SetCanFocus(false) portInput.SetEditable(false) wg.Wait() // do this after server shuts down on = false buttonSwitch.SetLabel("Start") browseButton.SetCanFocus(true) portInput.SetEditable(true) }() } }) portInput.Connect("insert-text", func(_ *gtk.Entry, input string) { if _, err := strconv.Atoi(input); err != nil { // if input is not numeric, don't insert portInput.StopEmission("insert-text") } else { // stop if port number is invalid / out of range currentInput, _ := portInput.GetText() nextInput := currentInput + input n, _ := strconv.Atoi(nextInput) if n < 0 || n > 65535 { portInput.StopEmission("insert-text") } } }) browseButton.Connect("selection-changed", func() { /* when different directory is selected, * shut down the server and show warning message */ if on { server.Shutdown(context.TODO()) statusLabel.SetMarkup("Server was terminated because root directory changed.") } }) aboutButton.Connect("clicked", func() { aboutWindow, _ := gtk.WindowNew(gtk.WINDOW_TOPLEVEL) aboutWindow.SetTitle("About - GoFileServer") aboutWindow.SetDefaultSize(420, 180) aboutWindow.SetResizable(false) aboutWindow.SetPosition(gtk.WIN_POS_MOUSE) box, _ := gtk.BoxNew(gtk.ORIENTATION_VERTICAL, 10) titleLabel, _ := gtk.LabelNew("") titleLabel.SetMarkup(fmt.Sprintf("GoFileServer %s", APP_VERSION)) box.PackStart(titleLabel, true, true, 5) copyrightLabel, _ := gtk.LabelNew("Copyright (c) 2022 Vidhu Kant Sharma") box.PackStart(copyrightLabel, true, true, 5) urlLabel, _ := gtk.LabelNew("") urlLabel.SetMarkup("https://github.com/MikunoNaka/GoFileServer") box.PackStart(urlLabel, true, true, 5) gplLabel, _ := gtk.LabelNew("") gplLabel.SetMarkup("This program comes with absolutely no warranty.\nSee the GNU General Public License Version 3 or later for details.") gplLabel.SetLineWrap(true) gplLabel.SetLineWrapMode(pango.WrapMode(gtk.ALIGN_START)) gplLabel.SetJustify(gtk.JUSTIFY_CENTER) box.PackStart(gplLabel, true, true, 5) buttonBox, _ := gtk.BoxNew(gtk.ORIENTATION_HORIZONTAL, 5) closeButton, _ := gtk.ButtonNewWithLabel("Close") buttonBox.PackEnd(closeButton, false, true, 5) box.PackEnd(buttonBox, true, true, 5) closeButton.Connect("clicked", func() { aboutWindow.Close() }) aboutWindow.Add(box) aboutWindow.ShowAll() }) MAIN_WINDOW.Add(box) MAIN_WINDOW.ShowAll() }