Skip to content
Open
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
152 changes: 152 additions & 0 deletions adapter/diagnostic.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
package adapter

import (
"encoding/json"
"io"
"log"
"net/http"
"os"
"strconv"
"strings"
"sync"
"time"

svg "github.com/ajstarks/svgo"
"github.com/google/uuid"
"github.com/jodi-ivan/numbered-notation-xml/internal/utils"
"github.com/jodi-ivan/numbered-notation-xml/svc/repository"
"github.com/jodi-ivan/numbered-notation-xml/svc/usecase"
"github.com/jodi-ivan/numbered-notation-xml/utils/canvas"
"github.com/jodi-ivan/numbered-notation-xml/utils/params"
"github.com/julienschmidt/httprouter"
)

type DiagnosticHTTP struct {
Usecase usecase.Usecase
Interrupt chan os.Signal
Repo repository.Repository
}

// SSEvent represents a single server-sent event packet.
type SSEvent struct {
ID string `json:"id"` // Optional: Unique identifier for the event
Event string `json:"event"` // Optional: Custom event name (e.g., "update", "chat_message")
Data []byte `json:"data"` // Required: The main payload
Retry int `json:"retry"` // Optional: Reconnection timeout value in milliseconds
}

func (dh *DiagnosticHTTP) ServeHTTP(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
target := svg.New(io.Discard)

canv := canvas.NewCanvasWithDelegator(target, &CanvasDelegator{})

w.Header().Set("Content-Type", "text/event-stream")
w.Header().Set("Cache-Control", "no-cache")
w.Header().Set("Connection", "keep-alive")
w.WriteHeader(http.StatusOK)

no, vars, err := utils.ParseHymnWithVariant(ps.ByName("scope"))
if err != nil {
w.Write([]byte("Invalid param" + err.Error()))
if flusher, ok := w.(http.Flusher); ok {
flusher.Flush()
}
return
}

mode := r.FormValue("focus")

focusMode, err := strconv.ParseBool(mode)
if mode != "" && err != nil {
log.Printf("[ServeHTTP] invalid mode: %v", err.Error())
w.WriteHeader(http.StatusBadRequest)
w.Write([]byte("Invalid URL"))
if flusher, ok := w.(http.Flusher); ok {
flusher.Flush()
}
return
}

timeout := time.NewTimer(2 * time.Second)
defer timeout.Stop()

dig := &params.DiagParam{
VerseSyllMatch: make(chan map[int]bool, 1),
VerseDiagnostic: make(chan params.VerseDiagnostic),
Finish: make(chan bool, 1),
Mu: &sync.RWMutex{},
MapMtx: &sync.Map{},
}

go func() {
prm := &params.Param{
Verse: 3, // hardcoded to trigger the load verse mechanism
SingleVerseMode: focusMode,
Diagnostic: dig,
}
rctx := params.NewParamContext(r.Context(), prm)
v := []string{}
if vars != "" {
v = append(v, vars)
}
err := dh.Usecase.RenderHymn(rctx, canv, no, v...)
log.Println("try render", err)
}()

for {
select {

case <-timeout.C:
log.Println("timeout")
body := []string{
"id: " + uuid.NewString() + "\n",
"event: close\n",
"data: stream_finished \n\n",
}
// body, _ := json.Marshal(SSEvent{
// ID: uuid.NewString(),
// Event: "data",
// Data: s,
// })

w.Write([]byte(strings.Join(body, "")))
if flusher, ok := w.(http.Flusher); ok {
flusher.Flush()
}
return

case intr := <-dh.Interrupt:
log.Println("inside the server", intr)
return
case <-dig.VerseDiagnostic:

syncMap := map[int]interface{}{}
dig.MapMtx.Range(func(key, value any) bool {
syncMap[key.(int)] = value
return true
})

s, _ := json.Marshal(syncMap)
body := []string{
"id: " + uuid.NewString() + "\n",
"event: data\n",
"data: " + string(s) + "\n\n",
}

w.Write([]byte(strings.Join(body, "")))
if flusher, ok := w.(http.Flusher); ok {
flusher.Flush()
}
// time.Sleep(1 * time.Second)
// timeout.Reset(time.Millisecond * 100)

// case <-dig.Finish:
// log.Println("finishing")
// // time.Sleep(1 * time.Second)
// if flusher, ok := w.(http.Flusher); ok {
// flusher.Flush()
// }
// return
}
}
}
8 changes: 4 additions & 4 deletions adapter/render_to_string.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,12 @@ import (
"github.com/jodi-ivan/numbered-notation-xml/utils/canvas"
)

type RenderStringCanvasDelegator struct{}
type CanvasDelegator struct{}

func (rscd *RenderStringCanvasDelegator) OnBeforeStartWrite() {
func (rscd *CanvasDelegator) OnBeforeStartWrite() {
// no pre operation needed for string operation
}
func (rscd *RenderStringCanvasDelegator) OnError(err error) canvas.DelegatorErrorFlowControl {
func (rscd *CanvasDelegator) OnError(err error) canvas.DelegatorErrorFlowControl {
return canvas.DelegatorErrorFlowControlStop
}

Expand All @@ -30,7 +30,7 @@ func NewRenderString(u usecase.Usecase) *RenderString {
}

func (rs *RenderString) RenderHymn(ctx context.Context, buf *bytes.Buffer, number int, variant ...string) (string, error) {
canv := canvas.NewCanvasWithDelegator(svg.New(buf), &RenderStringCanvasDelegator{})
canv := canvas.NewCanvasWithDelegator(svg.New(buf), &CanvasDelegator{})
err := rs.usecase.RenderHymn(ctx, canv, number, variant...)
if err != nil {
return "", err
Expand Down
36 changes: 31 additions & 5 deletions adapter/renderer.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"github.com/jodi-ivan/numbered-notation-xml/svc/repository"
"github.com/jodi-ivan/numbered-notation-xml/svc/usecase"
"github.com/jodi-ivan/numbered-notation-xml/utils/canvas"
"github.com/jodi-ivan/numbered-notation-xml/utils/params"
"github.com/julienschmidt/httprouter"
)

Expand All @@ -30,8 +31,8 @@ func (cdh *CanvasDelegatorHTTP) OnError(err error) canvas.DelegatorErrorFlowCont

} else if errors.Is(err, repository.ErrHymnHasMoreThanOneVariant) {
// Perform the redirect
http.Redirect(cdh.w, cdh.r, cdh.r.URL.Path+"a", http.StatusSeeOther)

cdh.r.URL.Path += "a"
http.Redirect(cdh.w, cdh.r, cdh.r.URL.RequestURI(), http.StatusSeeOther)
return canvas.DelegatorErrorFlowControlStop

}
Expand Down Expand Up @@ -60,22 +61,47 @@ func (rh *RenderHTTP) ServeHTTP(w http.ResponseWriter, r *http.Request, ps httpr
num, err := strconv.Atoi(raw)
if err != nil {
if len(raw) == 0 {
log.Printf("invalid number: %v", err.Error())
log.Printf("[ServeHTTP] invalid number: %v", err.Error())
w.WriteHeader(http.StatusBadRequest)
w.Write([]byte("Invalid URL"))
return
}
num, err = strconv.Atoi(raw[0 : len(raw)-1])
if err != nil {
log.Printf("invalid number: %v", err.Error())
log.Printf("[ServeHTTP] invalid number: %v", err.Error())
w.WriteHeader(http.StatusBadRequest)
w.Write([]byte("Invalid URL"))
return
}
variant = []string{string(raw[len(raw)-1])}
}

err = rh.usecase.RenderHymn(r.Context(), canv, num, variant...)
verseRaw := r.FormValue("verse")

verseNo, err := strconv.Atoi(verseRaw)
if verseRaw != "" && err != nil {
log.Printf("[ServeHTTP] invalid verse: %v", err.Error())
w.WriteHeader(http.StatusBadRequest)
w.Write([]byte("Invalid URL"))
return
}

mode := r.FormValue("focus")

focusMode, err := strconv.ParseBool(mode)
if mode != "" && err != nil {
log.Printf("[ServeHTTP] invalid mode: %v", err.Error())
w.WriteHeader(http.StatusBadRequest)
w.Write([]byte("Invalid URL"))
return
}

prm := &params.Param{
Verse: verseNo,
SingleVerseMode: focusMode,
}

err = rh.usecase.RenderHymn(params.NewParamContext(r.Context(), prm), canv, num, variant...)
if err != nil {
delegator.OnError(err)
}
Expand Down
9 changes: 8 additions & 1 deletion cmd/rest/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,14 +63,21 @@ func main() {
Db: db,
})

sigs := make(chan os.Signal, 1)

ws.Register("GET", "/internal/diagnostic/verse/:scope", &adapter.DiagnosticHTTP{
Usecase: usecaseMod,
Interrupt: sigs,
Repo: repo,
})

err = ws.Serve(cfg.Webserver.Port)
if err != nil {
log.Printf("Failed to start the server. Err: %s", err.Error())
os.Exit(1)
return
}

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

Expand Down
97 changes: 97 additions & 0 deletions diagnostics/verse_diag.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
package diagnostics

import (
"context"
"log"
"time"

"github.com/jodi-ivan/numbered-notation-xml/internal/entity"
"github.com/jodi-ivan/numbered-notation-xml/internal/musicxml"
"github.com/jodi-ivan/numbered-notation-xml/internal/verse"
"github.com/jodi-ivan/numbered-notation-xml/utils/params"
)

var verseDiagnostic *VerseDiagnostic

type VerseDiagnostic struct {
Matcher verse.SyllableMatch
}

func (vd *VerseDiagnostic) IsVowel(char rune) bool {
return vd.Matcher.IsVowel(char)
}
func (vd *VerseDiagnostic) ApplyElision(syllText string, combine bool) []musicxml.LyricText {
return vd.Matcher.ApplyElision(syllText, combine)
}
func (vd *VerseDiagnostic) LoadOtherVerse(ctx context.Context, notes []*entity.NoteRenderer, metadata *entity.HymnMetaData, startPos int, offset map[int]int, prevRepeatInfos []*musicxml.RepeatInfo) (map[int]int, int) {
all := map[int]int{}
// singleResult := map[int]bool{}

timeout := time.NewTimer(10 * time.Second)
defer timeout.Stop()

prm, _ := params.GetParamFromContext(ctx)
go func() {

for {
select {
case data := <-prm.Diagnostic.VerseSyllMatch:
currentData := data
prm.Diagnostic.Mu.RLock()
for k, v := range currentData {
prm.Diagnostic.MapMtx.Store(k, v)
}

prm.Diagnostic.Mu.RUnlock()

prm.Diagnostic.VerseDiagnostic <- params.VerseDiagnostic{
SingleMode: currentData,
}

case <-timeout.C:
log.Println("timeout")

prm.Diagnostic.Finish <- true
return

}

}
}()

allOffset := map[int]map[int]int{}
for i := 2; i <= len(metadata.Verse)+1; i++ {
newParam := &params.Param{
Verse: i,
SingleVerseMode: prm.SingleVerseMode,
Diagnostic: prm.Diagnostic,
}

rctx := params.NewParamContext(ctx, newParam)
allOffset[i], _ = vd.Matcher.LoadOtherVerse(rctx, notes, metadata, startPos, offset, prevRepeatInfos)

for i, v := range allOffset {
for _, off := range v {
all[i] = off
}
}

}
// prm.Diagnostic.Finish <- true

return all, 0

}
func (vd *VerseDiagnostic) LoadVerse(ctx context.Context, targetVerse int, clear bool, notes []*entity.NoteRenderer, metadata *entity.HymnMetaData, startPos int, prevRepeatInfos []*musicxml.RepeatInfo) (int, int) {
return vd.Matcher.LoadVerse(ctx, targetVerse, clear, notes, metadata, startPos, prevRepeatInfos)
}

func GetVerseDiagnostic(matcher verse.SyllableMatch) *VerseDiagnostic {
if verseDiagnostic == nil {
verseDiagnostic = &VerseDiagnostic{
Matcher: matcher,
}
}

return verseDiagnostic
}
Loading