Clean project layout and fix release workflow
Some checks failed
build-windows-exe / build (push) Failing after 1m13s

This commit is contained in:
messypy
2026-07-27 23:01:24 +09:00
parent 3ff33e40e3
commit dd165d9301
80 changed files with 2908 additions and 5661 deletions

View File

@@ -1,15 +1,16 @@
package app
import (
"bytes"
"fmt"
"io"
"log"
"os"
"path/filepath"
"regexp"
"strings"
"time"
"unicode/utf8"
"syscall"
"time"
"unsafe"
)
@@ -24,6 +25,8 @@ var (
enteringRoomPattern = regexp.MustCompile(`\[Behaviour\]\s+(?:Entering Room|Joining or Creating Room):\s+(.+)$`)
)
const vrchatLogTailBytes = 2 * 1024 * 1024
type vrcLogState struct {
location string
worldID string
@@ -94,13 +97,13 @@ func watchVrchatLog() {
path, err := findLatestVrchatLog()
if err != nil {
log.Printf("vrchat log not found: %v", err)
time.Sleep(2 * time.Second)
time.Sleep(currentGUIRefreshInterval())
continue
}
info, err := os.Stat(path)
if err != nil {
log.Printf("vrchat log stat failed: %v", err)
time.Sleep(2 * time.Second)
time.Sleep(currentGUIRefreshInterval())
continue
}
log.Printf("vrchat log loop path=%s size=%d last=%d", path, info.Size(), lastSize)
@@ -119,7 +122,7 @@ func watchVrchatLog() {
} else {
lastSize = info.Size()
}
time.Sleep(2 * time.Second)
time.Sleep(currentGUIRefreshInterval())
continue
}
@@ -128,10 +131,10 @@ func watchVrchatLog() {
}
if info.Size() > lastSize {
log.Printf("vrchat log updated path=%s old=%d new=%d", path, lastSize, info.Size())
b, err := os.ReadFile(path)
b, err := readFileFrom(path, lastSize)
if err != nil {
log.Printf("vrchat log read failed: %v", err)
time.Sleep(2 * time.Second)
time.Sleep(currentGUIRefreshInterval())
continue
}
text := decodeVRChatLog(b)
@@ -146,16 +149,13 @@ func watchVrchatLog() {
if err := appendRuntimeLog("VRC LOG", line); err != nil {
log.Printf("append runtime log failed: %v", err)
}
at := extractLineTime(line)
if changed, worldLabel := updateWorldState(state, line); changed {
log.Printf("world changed: %s", worldLabel)
state.presentSet = map[string]struct{}{}
SetCurrentWorld(worldLabel)
if tracker := GetGuestTracker(); tracker != nil {
tracker.SetCurrentInstance(worldLabel)
}
handleWorldChange(state, worldLabel, at)
_ = appendRuntimeLog("VRC WORLD", worldLabel)
}
at := extractLineTime(line)
if m := joinPattern.FindStringSubmatch(line); len(m) == 2 {
name := strings.TrimSpace(m[1])
log.Printf("join detected: %s", name)
@@ -164,6 +164,9 @@ func watchVrchatLog() {
if tracker := GetGuestTracker(); tracker != nil {
tracker.MarkJoin(name, at)
}
if tracker := GetJoinLeaveEventTracker(); tracker != nil {
tracker.Record("join", name, len(state.presentSet), at)
}
out := fmt.Sprintf("[join] %s (%d)", name, len(state.presentSet))
log.Print(out)
if err := appendJoinLeaveLog(out); err != nil {
@@ -179,6 +182,9 @@ func watchVrchatLog() {
if tracker := GetGuestTracker(); tracker != nil {
tracker.MarkLeave(name, at)
}
if tracker := GetJoinLeaveEventTracker(); tracker != nil {
tracker.Record("leave", name, len(state.presentSet), at)
}
out := fmt.Sprintf("[leave] %s (%d)", name, len(state.presentSet))
log.Print(out)
if err := appendJoinLeaveLog(out); err != nil {
@@ -190,10 +196,55 @@ func watchVrchatLog() {
log.Printf("vrchat log batch done join=%d leave=%d", joinHits, leaveHits)
lastSize = info.Size()
}
time.Sleep(2 * time.Second)
time.Sleep(currentGUIRefreshInterval())
}
}
func readFileFrom(path string, offset int64) ([]byte, error) {
f, err := os.Open(path)
if err != nil {
return nil, err
}
defer f.Close()
if offset > 0 {
if _, err := f.Seek(offset, 0); err != nil {
return nil, err
}
}
return io.ReadAll(f)
}
func readFileTail(path string, maxBytes int64) ([]byte, error) {
if maxBytes <= 0 {
return os.ReadFile(path)
}
info, err := os.Stat(path)
if err != nil {
return nil, err
}
if info.Size() <= maxBytes {
return os.ReadFile(path)
}
f, err := os.Open(path)
if err != nil {
return nil, err
}
defer f.Close()
if _, err := f.Seek(info.Size()-maxBytes, 0); err != nil {
return nil, err
}
b, err := io.ReadAll(f)
if err != nil {
return nil, err
}
for i, c := range b {
if c == '\n' && i+1 < len(b) {
return b[i+1:], nil
}
}
return b, nil
}
func scanExistingVrchatLog(path string, state *vrcLogState) error {
b, err := os.ReadFile(path)
if err != nil {
@@ -208,13 +259,18 @@ func scanExistingVrchatLog(path string, state *vrcLogState) error {
state.initialized = false
text := decodeVRChatLog(b)
var lastWorldAt time.Time
sawWorldChange := false
for _, raw := range strings.Split(text, "\n") {
line := strings.TrimRight(raw, "\r")
if line == "" {
continue
}
if changed, _ := updateWorldState(state, line); changed {
sawWorldChange = true
lastWorldAt = extractLineTime(line)
state.presentSet = map[string]struct{}{}
handleWorldChange(state, state.worldName, lastWorldAt)
}
if m := joinPattern.FindStringSubmatch(line); len(m) == 2 {
name := strings.TrimSpace(m[1])
@@ -235,10 +291,12 @@ func scanExistingVrchatLog(path string, state *vrcLogState) error {
}
state.initialized = true
if state.worldName != "" {
log.Printf("world changed: %s", state.worldName)
SetCurrentWorld(state.worldName)
if tracker := GetGuestTracker(); tracker != nil {
tracker.SetCurrentInstance(state.worldName)
if !sawWorldChange {
log.Printf("world changed: %s", state.worldName)
if lastWorldAt.IsZero() {
lastWorldAt = time.Now()
}
handleWorldChange(state, state.worldName, lastWorldAt)
}
if err := appendRuntimeLog("VRC WORLD", state.worldName); err != nil {
log.Printf("append world log failed: %v", err)
@@ -251,9 +309,6 @@ func decodeVRChatLog(b []byte) string {
if len(b) == 0 {
return ""
}
if utf8.Valid(b) {
return strings.TrimPrefix(string(b), "\ufeff")
}
if len(b) >= 2 {
if b[0] == 0xff && b[1] == 0xfe {
u16 := make([]uint16, 0, (len(b)-2)/2)
@@ -270,12 +325,23 @@ func decodeVRChatLog(b []byte) string {
return strings.TrimPrefix(fixMojibake(syscall.UTF16ToString(u16)), "\ufeff")
}
}
for _, cp := range []uint32{932, 1252, 65001} {
if decoded := decodeWithCodePage(b, cp); decoded != "" {
return strings.TrimPrefix(fixMojibake(decoded), "\ufeff")
lines := bytes.Split(b, []byte{'\n'})
var out strings.Builder
for i, raw := range lines {
if i > 0 {
out.WriteByte('\n')
}
out.WriteString(decodeVRChatLogLine(raw))
}
return strings.TrimPrefix(fixMojibake(string(b)), "\ufeff")
return strings.TrimPrefix(out.String(), "\ufeff")
}
func decodeVRChatLogLine(b []byte) string {
line := bytes.TrimRight(b, "\r")
if len(line) == 0 {
return ""
}
return strings.TrimPrefix(string(line), "\ufeff")
}
func DecodeVRChatLog(b []byte) string {
@@ -402,6 +468,9 @@ func updateWorldState(state *vrcLogState, line string) (bool, string) {
nextLocation = worldID + ":" + instanceID
}
}
if nextLocation == "" && roomTitle != "" {
nextLocation = roomTitle
}
if nextLocation != "" && nextLocation != state.location {
state.location = nextLocation
@@ -421,8 +490,8 @@ func updateWorldState(state *vrcLogState, line string) (bool, string) {
}
if state.initialized {
log.Printf("world changed: %s", label)
_ = appendRuntimeLog("VRC WORLD", label)
}
state.initialized = true
return true, label
}
@@ -489,3 +558,16 @@ func extractLineTime(line string) time.Time {
}
return time.Now()
}
func handleWorldChange(state *vrcLogState, worldLabel string, at time.Time) {
if state == nil {
return
}
SetCurrentWorldVisit(worldLabel, at)
if tracker := GetWorldHistoryTracker(); tracker != nil {
tracker.ObserveWorld(worldLabel, state.worldID, state.instanceID, at)
}
if tracker := GetGuestTracker(); tracker != nil {
tracker.ResetCurrentInstance(worldLabel)
}
}