Files

258 lines
6.2 KiB
Go

package modular
import (
"context"
"fmt"
"io"
"math/rand"
"net"
"sync"
"time"
"grpccanary/lib/grpc/modular/protoapi"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/reflection"
"google.golang.org/grpc/status"
)
type UploadedFile struct {
FileName string
Content []byte
UploadedAt int64
}
type AlertSubscriber struct {
ClientId string
Channel chan *protoapi.AlertMessage
}
var (
fileStore = make(map[string]*UploadedFile)
storeMu sync.RWMutex
subscribers = make(map[string]*AlertSubscriber)
subMu sync.Mutex
)
func publishAlert(alert *protoapi.AlertMessage) {
subMu.Lock()
defer subMu.Unlock()
for _, sub := range subscribers {
select {
case sub.Channel <- alert:
default:
fmt.Printf("Alert channel blocked for client %s, dropping event\n", sub.ClientId)
}
}
}
func random(min, max int, src rand.Source) int {
return rand.New(src).Intn(max-min) + min
}
func getString(len int64, src rand.Source) string {
temp := ""
startChar := "!"
var i int64 = 1
for {
myRand := random(0, 94, src)
newChar := string(startChar[0] + byte(myRand))
temp = temp + newChar
if i == len {
break
}
i++
}
return temp
}
// 1. Core Server Implementation
type CoreServer struct {
protoapi.UnimplementedCoreServiceServer
}
func (CoreServer) GetDate(ctx context.Context, r *protoapi.RequestDateTime) (*protoapi.DateTime, error) {
currentTime := time.Now()
return &protoapi.DateTime{
Value: currentTime.String(),
}, nil
}
func (CoreServer) UpdateSensingData(ctx context.Context, r *protoapi.SensingData) (*protoapi.SensingResponse, error) {
fmt.Printf("[Modular Core] Received sensing data - Device: %s, Temp: %.2f°C, Humid: %.2f%%\n", r.GetDeviceId(), r.GetTemperature(), r.GetHumidity())
if r.GetTemperature() > 40.0 {
fmt.Printf("⚠️ [Modular Core] Critical temperature detected: %.2f°C! Publishing warning...\n", r.GetTemperature())
publishAlert(&protoapi.AlertMessage{
AlertId: fmt.Sprintf("alert-%d", time.Now().UnixNano()),
DeviceId: r.GetDeviceId(),
Message: fmt.Sprintf("Critical high temperature: %.2f°C (Humidity: %.2f%%)", r.GetTemperature(), r.GetHumidity()),
Timestamp: time.Now().Unix(),
})
}
return &protoapi.SensingResponse{
Success: true,
Message: fmt.Sprintf("Sensing data updated successfully for device %s", r.GetDeviceId()),
}, nil
}
func (CoreServer) GetRandomPass(ctx context.Context, r *protoapi.RequestPass) (*protoapi.RandomPass, error) {
src := rand.NewSource(r.GetSeed())
temp := getString(r.GetLength(), src)
return &protoapi.RandomPass{
Password: temp,
}, nil
}
// 2. File Transfer Server Implementation
type FileTransferServer struct {
protoapi.UnimplementedFileTransferServiceServer
}
func (FileTransferServer) UploadFile(stream protoapi.FileTransferService_UploadFileServer) error {
var totalBytes int64
var fileName string
var buffer []byte
for {
chunk, err := stream.Recv()
if err == io.EOF {
fmt.Printf("[Modular File] File upload completed. Received %d bytes for file '%s'\n", totalBytes, fileName)
if fileName != "" {
storeMu.Lock()
fileStore[fileName] = &UploadedFile{
FileName: fileName,
Content: buffer,
UploadedAt: time.Now().Unix(),
}
storeMu.Unlock()
}
return stream.SendAndClose(&protoapi.UploadStatus{
Success: true,
Message: fmt.Sprintf("File '%s' uploaded successfully.", fileName),
BytesUploaded: totalBytes,
})
}
if err != nil {
fmt.Println("[Modular File] File upload error:", err)
return err
}
if fileName == "" {
fileName = chunk.GetFileName()
}
buffer = append(buffer, chunk.GetContent()...)
totalBytes += int64(len(chunk.GetContent()))
}
}
func (FileTransferServer) ListFiles(ctx context.Context, r *protoapi.EmptyRequest) (*protoapi.FileList, error) {
storeMu.RLock()
defer storeMu.RUnlock()
var files []*protoapi.FileMetadata
for _, f := range fileStore {
files = append(files, &protoapi.FileMetadata{
FileName: f.FileName,
FileSize: int64(len(f.Content)),
UploadedAt: f.UploadedAt,
})
}
return &protoapi.FileList{Files: files}, nil
}
func (FileTransferServer) DownloadFile(r *protoapi.DownloadRequest, stream protoapi.FileTransferService_DownloadFileServer) error {
storeMu.RLock()
f, exists := fileStore[r.GetFileName()]
storeMu.RUnlock()
if !exists {
return status.Errorf(codes.NotFound, "file %s not found", r.GetFileName())
}
chunkSize := 1024
totalBytes := len(f.Content)
for i := 0; i < totalBytes; i += chunkSize {
end := i + chunkSize
if end > totalBytes {
end = totalBytes
}
err := stream.Send(&protoapi.FileChunk{
FileName: f.FileName,
Content: f.Content[i:end],
})
if err != nil {
return err
}
}
return nil
}
// 3. Alert Server Implementation
type AlertServer struct {
protoapi.UnimplementedAlertServiceServer
}
func (AlertServer) SubscribeAlerts(r *protoapi.AlertSubscription, stream protoapi.AlertService_SubscribeAlertsServer) error {
clientId := r.GetClientId()
ch := make(chan *protoapi.AlertMessage, 10)
sub := &AlertSubscriber{
ClientId: clientId,
Channel: ch,
}
subMu.Lock()
subscribers[clientId] = sub
subMu.Unlock()
fmt.Printf("[Modular Alert] Client %s subscribed to alerts on topic '%s'\n", clientId, r.GetTopic())
for {
select {
case alert := <-ch:
err := stream.Send(alert)
if err != nil {
subMu.Lock()
delete(subscribers, clientId)
subMu.Unlock()
fmt.Printf("[Modular Alert] Client %s alert subscription disconnected: %v\n", clientId, err)
return err
}
case <-stream.Context().Done():
subMu.Lock()
delete(subscribers, clientId)
subMu.Unlock()
fmt.Printf("[Modular Alert] Client %s unsubscribed (context done)\n", clientId)
return nil
}
}
}
func ServerRun(addr string) {
server := grpc.NewServer()
// Register multiple services on the same gRPC server
protoapi.RegisterCoreServiceServer(server, CoreServer{})
protoapi.RegisterFileTransferServiceServer(server, FileTransferServer{})
protoapi.RegisterAlertServiceServer(server, AlertServer{})
reflection.Register(server)
listen, err := net.Listen("tcp", addr)
if err != nil {
fmt.Println(err)
return
}
fmt.Printf("[Modular Server] Listening on %s...\n", addr)
server.Serve(listen)
}