Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
.build/
.idea/
pat
pat*.pkg
docker-data/
.aider*
*.swp
libportapat/build/*.jar
libportapat/build/*.aar
52 changes: 52 additions & 0 deletions cmd/portapat/portapat_main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package main

import (
"github.com/la5nta/pat/libportapat"
"log"
"os"
"os/signal"
"path/filepath"
"syscall"
)

/*
This file is a launcher for the portable (libportapat) version of PAT.
It is primarily designed for being embedded into an Android's apk
package - as a library.
This package is designed to start this library on a regular PC/SBC,
for debugging or using PAT as a portable app.
*/

func main() {
log.Println("Starting portapat...")
libportapat.SetRootPath(getDataDirectory())
libportapat.SetRandomizePort(false)

sigs := make(chan os.Signal, 1)
signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)

err := libportapat.Start()
if err != nil {
log.Fatalln("Unable to start portapat:", err)
}

log.Println("Open your browser and navigate to: ", libportapat.GetUrlForBrowsers())
_ = <-sigs
libportapat.Stop()
}

func getDataDirectory() string {
exePath, err := os.Executable()
if err != nil {
log.Fatalln("Failed to get exe path:", err)
}
dataDir := filepath.Join(filepath.Dir(exePath), "data")
if _, err := os.Stat(dataDir); os.IsNotExist(err) {
err = os.MkdirAll(dataDir, 0755)
if err != nil {
log.Fatalln("Failed to create data directory:", err)
}
}

return dataDir
}
17 changes: 9 additions & 8 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ require (
github.com/pd0mz/go-maidenhead v1.0.0
github.com/peterh/liner v1.2.2
github.com/spf13/pflag v1.0.10
golang.org/x/sync v0.16.0
golang.org/x/sync v0.19.0
)

require (
Expand All @@ -36,12 +36,13 @@ require (
github.com/paulrosania/go-charset v0.0.0-20190326053356-55c9d7a5834c // indirect
github.com/rivo/uniseg v0.4.7 // indirect
go.uber.org/multierr v1.11.0 // indirect
golang.org/x/crypto v0.41.0 // indirect
golang.org/x/mod v0.22.0 // indirect
golang.org/x/net v0.43.0 // indirect
golang.org/x/sys v0.35.0 // indirect
golang.org/x/telemetry v0.0.0-20240522233618-39ace7a40ae7 // indirect
golang.org/x/term v0.34.0 // indirect
golang.org/x/tools v0.29.0 // indirect
golang.org/x/crypto v0.46.0 // indirect
golang.org/x/mobile v0.0.0-20251209145715-2553ed8ce294 // indirect
golang.org/x/mod v0.31.0 // indirect
golang.org/x/net v0.48.0 // indirect
golang.org/x/sys v0.39.0 // indirect
golang.org/x/telemetry v0.0.0-20251203150158-8fff8a5912fc // indirect
golang.org/x/term v0.38.0 // indirect
golang.org/x/tools v0.40.0 // indirect
golang.org/x/vuln v1.1.4 // indirect
)
18 changes: 18 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -87,22 +87,40 @@ go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
golang.org/x/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4=
golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc=
golang.org/x/crypto v0.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU=
golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0=
golang.org/x/mobile v0.0.0-20251209145715-2553ed8ce294 h1:Cr6kbEvA6nqvdHynE4CtVKlqpZB9dS1Jva/6IsHA19g=
golang.org/x/mobile v0.0.0-20251209145715-2553ed8ce294/go.mod h1:RdZ+3sb4CVgpCFnzv+I4haEpwqFfsfzlLHs3L7ok+e0=
golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4=
golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
golang.org/x/mod v0.31.0 h1:HaW9xtz0+kOcWKwli0ZXy79Ix+UW/vOfmWI5QVd2tgI=
golang.org/x/mod v0.31.0/go.mod h1:43JraMp9cGx1Rx3AqioxrbrhNsLl2l/iNAvuBkrezpg=
golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE=
golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg=
golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU=
golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY=
golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw=
golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sys v0.0.0-20211117180635-dee7805ff2e1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=
golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk=
golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/telemetry v0.0.0-20240522233618-39ace7a40ae7 h1:FemxDzfMUcK2f3YY4H+05K9CDzbSVr2+q/JKN45pey0=
golang.org/x/telemetry v0.0.0-20240522233618-39ace7a40ae7/go.mod h1:pRgIJT+bRLFKnoM1ldnzKoxTIn14Yxz928LQRYYgIN0=
golang.org/x/telemetry v0.0.0-20251203150158-8fff8a5912fc h1:bH6xUXay0AIFMElXG2rQ4uiE+7ncwtiOdPfYK1NK2XA=
golang.org/x/telemetry v0.0.0-20251203150158-8fff8a5912fc/go.mod h1:hKdjCMrbv9skySur+Nek8Hd0uJ0GuxJIoIX2payrIdQ=
golang.org/x/term v0.34.0 h1:O/2T7POpk0ZZ7MAzMeWFSg6S5IpWd/RXDlM9hgM3DR4=
golang.org/x/term v0.34.0/go.mod h1:5jC53AEywhIVebHgPVeg0mj8OD3VO9OzclacVrqpaAw=
golang.org/x/term v0.38.0 h1:PQ5pkm/rLO6HnxFR7N2lJHOZX6Kez5Y1gDSJla6jo7Q=
golang.org/x/term v0.38.0/go.mod h1:bSEAKrOT1W+VSu9TSCMtoGEOUcKxOKgl3LE5QEF/xVg=
golang.org/x/tools v0.29.0 h1:Xx0h3TtM9rzQpQuR4dKLrdglAmCEN5Oi+P74JdhdzXE=
golang.org/x/tools v0.29.0/go.mod h1:KMQVMRsVxU6nHCFXrBPhDB8XncLNLM0lIy/F14RP588=
golang.org/x/tools v0.40.0 h1:yLkxfA+Qnul4cs9QA3KnlFu0lVmd8JJfoq+E41uSutA=
golang.org/x/tools v0.40.0/go.mod h1:Ik/tzLRlbscWpqqMRjyWYDisX8bG13FrdXp3o4Sr9lc=
golang.org/x/vuln v1.1.4 h1:Ju8QsuyhX3Hk8ma3CesTbO8vfJD9EvUBgHvkxHBzj0I=
golang.org/x/vuln v1.1.4/go.mod h1:F+45wmU18ym/ca5PLTPLsSzr2KppzswxPP603ldA67s=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
Expand Down
2 changes: 2 additions & 0 deletions libportapat/build/make.bash
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
#!/usr/bin/env bash
gomobile bind -v -target=android ..
66 changes: 66 additions & 0 deletions libportapat/config_related.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package libportapat

import (
"encoding/json"
"errors"
"fmt"
cfg2 "github.com/la5nta/pat/cfg"
"github.com/la5nta/pat/libportapat/porta_paths"
"log"
"math/rand"
"os"
"strings"
)

func SetRandomizePort(rndport bool) {
flagRandomisePort = rndport
}

func doRandomisePort() error {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of setting the custom port by rewriting the config file, you have two other options:

  1. Pass -addr [host:port] as arguments to the http command.
  2. Set env var PAT_HTTPADDR=[host:port] (this applies to all config options)

By doing this, you don't need to read/write the config file.

newPort := 48000 + rand.Intn(1000)
strConfig := ConfigRead()
var cfg cfg2.Config

if len(strConfig) < 1 || strings.Contains(strConfig, "!!! ERROR READING CONFIG") {
cfg = cfg2.DefaultConfig
} else {
err := json.Unmarshal([]byte(strConfig), &cfg)
if err != nil {
log.Println("Error unmarshalling: ", err)
cfg = cfg2.DefaultConfig
}
}

cfg.HTTPAddr = fmt.Sprintf("127.0.0.1:%d", newPort)

bytConfig, err := json.MarshalIndent(&cfg, "", "\t")
if err != nil {
return err
}
return ConfigWrite(string(bytConfig))
}

func ConfigRead() string {
cfgPath := porta_paths.GetPortaPath(porta_paths.PATH_KIND_CONFIG)
cfgBinary, err := os.ReadFile(cfgPath)
if err != nil {
fmt.Printf("Error reading config file: %s\n", err)
return "!!! ERROR READING CONFIG: " + err.Error()
}
return string(cfgBinary)
}

func ConfigWrite(newconf string) error {
var cfg cfg2.Config
errc := json.Unmarshal([]byte(newconf), &cfg)
if errc != nil {
return errors.New("Format error: " + errc.Error())
}

cfgPath := porta_paths.GetPortaPath(porta_paths.PATH_KIND_CONFIG)
err := os.WriteFile(cfgPath, []byte(newconf), 0644)
if err != nil {
fmt.Printf("Error writing config file: %s\n", err)
}
return nil
}
157 changes: 157 additions & 0 deletions libportapat/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
package libportapat

import (
"context"
"encoding/json"
"errors"
"fmt"
"github.com/la5nta/pat/api"
"github.com/la5nta/pat/app"
"github.com/la5nta/pat/cfg"
"github.com/la5nta/pat/cli"
"github.com/la5nta/pat/internal/buildinfo"
"github.com/la5nta/pat/libportapat/porta_paths"
"github.com/la5nta/pat/web_frontend"
"log"
"os"
"runtime"
"strings"
"sync"
)

func SetRootPath(rootPath string) {
porta_paths.SetRootPath(rootPath)
}

func prepareOptions(opts *app.Options) {
opts.MailboxPath = porta_paths.GetPortaPath(porta_paths.PATH_KIND_MAILBOX)
opts.FormsPath = porta_paths.GetPortaPath(porta_paths.PATH_KIND_FORMS)
opts.ConfigPath = porta_paths.GetPortaPath(porta_paths.PATH_KIND_CONFIG)
opts.PrehooksPath = porta_paths.GetPortaPath(porta_paths.PATH_KIND_PREHOOKS)
opts.LogPath = porta_paths.GetPortaPath(porta_paths.PATH_KIND_LOG)
opts.EventLogPath = porta_paths.GetPortaPath(porta_paths.PATH_KIND_EVENTLOG)
}

var appRunSync sync.Mutex
var stopChan = make(chan bool)

var flagRandomisePort = false

func Start() error {
fmt.Println("Start", runtime.GOOS)
if !appRunSync.TryLock() {
return errors.New("App is already running. If it is not so, try stopping the app via settings/killing the process.")
}
api.EmbeddedFS = web_frontend.EmbeddedFS
if flagRandomisePort {
eport := doRandomisePort()
if eport != nil {
return eport
}
}

go func() {
var opts app.Options
prepareOptions(&opts)

cmd, _, _, _ := cli.FindCommand([]string{"portapat", "http", ""})

for runApp(opts, cmd, []string{}) {
log.Println("App restarted?")
}
}()

return nil
}

func Stop() {
fmt.Println("Stop")
stopChan <- true
appRunSync.Unlock()
}

func runApp(opts app.Options, cmd app.Command, args []string) bool {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()

a := app.New(opts)
defer a.Close()

// Graceful shutdown/reload handling.
shouldReload := make(chan bool, 1)
done := make(chan struct{})
a.OnReload = func() error {
// Avoid reloading of bad config
if _, err := app.LoadConfig(opts.ConfigPath, cfg.DefaultConfig); err != nil {
return fmt.Errorf("bad config: %v", err)
}
cancel()
shouldReload <- true
return nil
}
go func() {
defer close(shouldReload)
dirtyDisconnectNext := true // Do the dirty disconnect right away
for {
select {
case _ = <-stopChan:
if ok := a.AbortActiveConnection(dirtyDisconnectNext); ok {
dirtyDisconnectNext = !dirtyDisconnectNext
continue
}
cancel()
shouldReload <- false
return
case <-done:
return
}
}
}()

// Run the app
a.Run(ctx, cmd, args)
close(done)
return <-shouldReload
}

// the app's version consists of the upstream version and the port's code version
// a.k.a. subverion (indicates both Java and GO changes)
const PP_SUBVERSION = "0.1"

func GetVersionStr(title string) string {
return fmt.Sprintf("%s v%s-%s [%s]", title, buildinfo.Version, PP_SUBVERSION, runtime.GOARCH)
}

func GetUrlForBrowsers() string {
cfgPath := porta_paths.GetPortaPath(porta_paths.PATH_KIND_CONFIG)
cfgBinary, err := os.ReadFile(cfgPath)
defUrl := "http://127.0.0.1:8080"

if err != nil {
// assuming the config does not exist
return defUrl
}

var config cfg.Config
err = json.Unmarshal(cfgBinary, &config)
if err != nil {
log.Println("Error parsing config file:", err)
return defUrl
}

// handling NULL ipv6 addresses
if strings.Contains(config.HTTPAddr, "[::]") {
parts := strings.Split(config.HTTPAddr, ":")
port := parts[len(parts)-1]
return "http://[::1]:" + port
}

// handling NULL ipv4 addresses
if strings.Contains(config.HTTPAddr, "0.0.0.0") {
parts := strings.Split(config.HTTPAddr, ":")
port := parts[len(parts)-1]
return "http://127.0.0.1:" + port
}

return "http://" + config.HTTPAddr
}
Loading