tangled
alpha
login
or
join now
angrydutchman.peedee.es
/
plcbundle
forked from
atscan.net/plcbundle
0
fork
atom
A Transparent and Verifiable Way to Sync the AT Protocol's PLC Directory
0
fork
atom
overview
issues
pulls
pipelines
ws bundle streaming
tree.fail
4 months ago
da2c208d
d287535f
+115
-117
1 changed file
expand all
collapse all
unified
split
cmd
plcbundle
server.go
+115
-117
cmd/plcbundle/server.go
···
1
1
package main
2
2
3
3
import (
4
4
+
"bufio"
4
5
"context"
5
6
"fmt"
6
7
"io"
···
360
361
// streamLive streams all historical data then continues with live updates
361
362
func streamLive(ctx context.Context, conn *websocket.Conn, mgr *bundle.Manager, startCursor int, done chan struct{}) error {
362
363
index := mgr.GetIndex()
364
364
+
bundles := index.GetBundles()
363
365
currentRecord := startCursor
364
366
365
365
-
// Step 1: Stream all historical bundles
366
366
-
bundles := index.GetBundles()
367
367
+
// Step 1: Stream historical bundles
367
368
if len(bundles) > 0 {
368
369
startBundleIdx := startCursor / bundle.BUNDLE_SIZE
369
370
startPosition := startCursor % bundle.BUNDLE_SIZE
370
371
371
372
if startBundleIdx < len(bundles) {
373
373
+
// Stream from startBundleIdx to end
372
374
for i := startBundleIdx; i < len(bundles); i++ {
373
373
-
select {
374
374
-
case <-done:
375
375
-
return nil // Client disconnected
376
376
-
default:
377
377
-
}
378
378
-
379
379
-
meta := bundles[i]
380
380
-
b, err := mgr.LoadBundle(ctx, meta.BundleNumber)
381
381
-
if err != nil {
382
382
-
fmt.Fprintf(os.Stderr, "Failed to load bundle %d: %v\n", meta.BundleNumber, err)
383
383
-
continue
384
384
-
}
385
385
-
386
386
-
startPos := 0
375
375
+
skipUntil := 0
387
376
if i == startBundleIdx {
388
388
-
startPos = startPosition
377
377
+
skipUntil = startPosition
389
378
}
390
379
391
391
-
for j := startPos; j < len(b.Operations); j++ {
392
392
-
select {
393
393
-
case <-done:
394
394
-
return nil
395
395
-
default:
396
396
-
}
397
397
-
398
398
-
if err := sendOperation(conn, b.Operations[j]); err != nil {
399
399
-
return err
400
400
-
}
401
401
-
currentRecord++
402
402
-
403
403
-
if currentRecord%1000 == 0 {
404
404
-
if err := conn.WriteMessage(websocket.PingMessage, nil); err != nil {
405
405
-
return err
406
406
-
}
407
407
-
}
380
380
+
newRecordCount, err := streamBundle(ctx, conn, mgr, bundles[i].BundleNumber, skipUntil, done)
381
381
+
if err != nil {
382
382
+
return err
408
383
}
384
384
+
currentRecord += newRecordCount
409
385
}
410
386
}
411
387
}
412
388
413
389
// Step 2: Stream current mempool
414
390
lastSeenMempoolCount := 0
415
415
-
mempoolOps, err := mgr.GetMempoolOperations()
416
416
-
if err == nil {
417
417
-
bundleRecordBase := len(bundles) * bundle.BUNDLE_SIZE
418
418
-
419
419
-
for i, op := range mempoolOps {
420
420
-
select {
421
421
-
case <-done:
422
422
-
return nil
423
423
-
default:
424
424
-
}
425
425
-
426
426
-
recordNum := bundleRecordBase + i
427
427
-
if recordNum < startCursor {
428
428
-
continue
429
429
-
}
430
430
-
431
431
-
if err := sendOperation(conn, op); err != nil {
432
432
-
return err
433
433
-
}
434
434
-
currentRecord++
435
435
-
lastSeenMempoolCount = i + 1
436
436
-
}
391
391
+
if err := streamMempool(conn, mgr, startCursor, len(bundles)*bundle.BUNDLE_SIZE, ¤tRecord, &lastSeenMempoolCount, done); err != nil {
392
392
+
return err
437
393
}
438
394
439
439
-
// Step 3: Enter live streaming loop
440
440
-
// Poll for new operations in mempool and new bundles
441
441
-
ticker := time.NewTicker(2 * time.Second)
395
395
+
// Step 3: Live streaming loop
396
396
+
ticker := time.NewTicker(500 * time.Millisecond)
442
397
defer ticker.Stop()
443
398
444
399
lastBundleCount := len(bundles)
445
445
-
446
400
fmt.Fprintf(os.Stderr, "WebSocket: entering live mode at cursor %d\n", currentRecord)
447
401
448
402
for {
···
452
406
return nil
453
407
454
408
case <-ticker.C:
455
455
-
// Refresh index to check for new bundles
409
409
+
// Check for new bundles
456
410
index = mgr.GetIndex()
457
411
bundles = index.GetBundles()
458
412
459
459
-
// Check if new bundles were created
460
413
if len(bundles) > lastBundleCount {
461
414
fmt.Fprintf(os.Stderr, "WebSocket: detected %d new bundle(s)\n", len(bundles)-lastBundleCount)
462
415
463
416
// Stream new bundles
464
417
for i := lastBundleCount; i < len(bundles); i++ {
465
465
-
select {
466
466
-
case <-done:
467
467
-
return nil
468
468
-
default:
469
469
-
}
470
470
-
471
471
-
meta := bundles[i]
472
472
-
b, err := mgr.LoadBundle(ctx, meta.BundleNumber)
418
418
+
newRecordCount, err := streamBundle(ctx, conn, mgr, bundles[i].BundleNumber, 0, done)
473
419
if err != nil {
474
474
-
fmt.Fprintf(os.Stderr, "Failed to load bundle %d: %v\n", meta.BundleNumber, err)
475
475
-
continue
420
420
+
return err
476
421
}
477
477
-
478
478
-
for _, op := range b.Operations {
479
479
-
select {
480
480
-
case <-done:
481
481
-
return nil
482
482
-
default:
483
483
-
}
484
484
-
485
485
-
if err := sendOperation(conn, op); err != nil {
486
486
-
return err
487
487
-
}
488
488
-
currentRecord++
489
489
-
490
490
-
if currentRecord%1000 == 0 {
491
491
-
if err := conn.WriteMessage(websocket.PingMessage, nil); err != nil {
492
492
-
return err
493
493
-
}
494
494
-
}
495
495
-
}
422
422
+
currentRecord += newRecordCount
496
423
}
497
424
498
425
lastBundleCount = len(bundles)
499
499
-
lastSeenMempoolCount = 0 // Reset mempool count after bundle creation
426
426
+
lastSeenMempoolCount = 0 // Reset after bundle creation
500
427
}
501
428
502
502
-
// Check for new operations in mempool
503
503
-
mempoolOps, err := mgr.GetMempoolOperations()
504
504
-
if err != nil {
505
505
-
continue
429
429
+
// Check for new mempool operations
430
430
+
if err := streamMempool(conn, mgr, startCursor, len(bundles)*bundle.BUNDLE_SIZE, ¤tRecord, &lastSeenMempoolCount, done); err != nil {
431
431
+
return err
432
432
+
}
433
433
+
434
434
+
// Keep-alive ping
435
435
+
if err := conn.WriteMessage(websocket.PingMessage, nil); err != nil {
436
436
+
return err
506
437
}
438
438
+
}
439
439
+
}
440
440
+
}
507
441
508
508
-
if len(mempoolOps) > lastSeenMempoolCount {
509
509
-
fmt.Fprintf(os.Stderr, "WebSocket: streaming %d new mempool operation(s)\n",
510
510
-
len(mempoolOps)-lastSeenMempoolCount)
442
442
+
// streamBundle streams a single bundle's operations without parsing
443
443
+
// Returns number of records streamed
444
444
+
func streamBundle(ctx context.Context, conn *websocket.Conn, mgr *bundle.Manager, bundleNumber int, skipUntil int, done chan struct{}) (int, error) {
445
445
+
reader, err := mgr.StreamBundleDecompressed(ctx, bundleNumber)
446
446
+
if err != nil {
447
447
+
fmt.Fprintf(os.Stderr, "Failed to stream bundle %d: %v\n", bundleNumber, err)
448
448
+
return 0, nil // Continue with next bundle
449
449
+
}
450
450
+
defer reader.Close()
451
451
+
452
452
+
scanner := bufio.NewScanner(reader)
453
453
+
buf := make([]byte, 0, 64*1024)
454
454
+
scanner.Buffer(buf, 1024*1024)
455
455
+
456
456
+
position := 0
457
457
+
streamed := 0
458
458
+
459
459
+
for scanner.Scan() {
460
460
+
line := scanner.Bytes()
461
461
+
if len(line) == 0 {
462
462
+
continue
463
463
+
}
464
464
+
465
465
+
// Skip until start position
466
466
+
if position < skipUntil {
467
467
+
position++
468
468
+
continue
469
469
+
}
470
470
+
471
471
+
select {
472
472
+
case <-done:
473
473
+
return streamed, nil
474
474
+
default:
475
475
+
}
476
476
+
477
477
+
// Send raw JSON line
478
478
+
if err := conn.WriteMessage(websocket.TextMessage, line); err != nil {
479
479
+
return streamed, err
480
480
+
}
481
481
+
482
482
+
position++
483
483
+
streamed++
484
484
+
485
485
+
if streamed%1000 == 0 {
486
486
+
conn.WriteMessage(websocket.PingMessage, nil)
487
487
+
}
488
488
+
}
489
489
+
490
490
+
if err := scanner.Err(); err != nil {
491
491
+
return streamed, fmt.Errorf("scanner error on bundle %d: %w", bundleNumber, err)
492
492
+
}
493
493
+
494
494
+
return streamed, nil
495
495
+
}
496
496
+
497
497
+
// streamMempool streams new operations from mempool
498
498
+
func streamMempool(conn *websocket.Conn, mgr *bundle.Manager, startCursor int, bundleRecordBase int, currentRecord *int, lastSeenCount *int, done chan struct{}) error {
499
499
+
mempoolOps, err := mgr.GetMempoolOperations()
500
500
+
if err != nil {
501
501
+
return nil // Not fatal
502
502
+
}
503
503
+
504
504
+
if len(mempoolOps) <= *lastSeenCount {
505
505
+
return nil // No new operations
506
506
+
}
511
507
512
512
-
// Stream new mempool operations
513
513
-
for i := lastSeenMempoolCount; i < len(mempoolOps); i++ {
514
514
-
select {
515
515
-
case <-done:
516
516
-
return nil
517
517
-
default:
518
518
-
}
508
508
+
newOps := len(mempoolOps) - *lastSeenCount
509
509
+
if newOps > 0 {
510
510
+
fmt.Fprintf(os.Stderr, "WebSocket: streaming %d new mempool operation(s)\n", newOps)
511
511
+
}
519
512
520
520
-
if err := sendOperation(conn, mempoolOps[i]); err != nil {
521
521
-
return err
522
522
-
}
523
523
-
currentRecord++
524
524
-
}
513
513
+
for i := *lastSeenCount; i < len(mempoolOps); i++ {
514
514
+
recordNum := bundleRecordBase + i
515
515
+
if recordNum < startCursor {
516
516
+
continue
517
517
+
}
525
518
526
526
-
lastSeenMempoolCount = len(mempoolOps)
527
527
-
}
519
519
+
select {
520
520
+
case <-done:
521
521
+
return nil
522
522
+
default:
523
523
+
}
528
524
529
529
-
// Send periodic ping to keep connection alive
530
530
-
if err := conn.WriteMessage(websocket.PingMessage, nil); err != nil {
531
531
-
return err
532
532
-
}
525
525
+
if err := sendOperation(conn, mempoolOps[i]); err != nil {
526
526
+
return err
533
527
}
528
528
+
*currentRecord++
534
529
}
530
530
+
531
531
+
*lastSeenCount = len(mempoolOps)
532
532
+
return nil
535
533
}
536
534
537
535
// sendOperation sends a single operation over WebSocket as raw JSON