package service import ( "context" "sync" "time" "github.com/cloudprint/cloudprint-backend/internal/model" "github.com/cloudprint/cloudprint-backend/internal/repository" "github.com/google/uuid" ) type PrintService struct { printerRepo *repository.PrinterRepository printJobRepo *repository.PrintJobRepository orderRepo *repository.OrderRepository subscribers map[int32]chan *model.PrintJob mu sync.RWMutex } func NewPrintService( printerRepo *repository.PrinterRepository, printJobRepo *repository.PrintJobRepository, orderRepo *repository.OrderRepository, ) *PrintService { s := &PrintService{ printerRepo: printerRepo, printJobRepo: printJobRepo, orderRepo: orderRepo, subscribers: make(map[int32]chan *model.PrintJob), } // Start heartbeat checker go s.checkPrinterHeartbeat() return s } func (s *PrintService) GetPrinters(ctx context.Context) ([]*model.Printer, error) { return s.printerRepo.GetAllActive(ctx) } func (s *PrintService) GetPrinterStatus(ctx context.Context, printerID int32) (*model.Printer, int64, error) { printer, err := s.printerRepo.GetByID(ctx, printerID) if err != nil || printer == nil { return nil, 0, err } queueCount, err := s.printJobRepo.GetQueueCount(ctx, printerID) if err != nil { return nil, 0, err } return printer, queueCount, nil } func (s *PrintService) RegisterPrinter(ctx context.Context, name, displayName, apiKey string) (*model.Printer, string, error) { // Check if printer already exists existing, err := s.printerRepo.GetByName(ctx, name) if err != nil { return nil, "", err } if existing != nil { return nil, "", nil } // Generate token for this printer token := uuid.New().String() printer := &model.Printer{ Name: name, DisplayName: displayName, APIKey: apiKey, IsActive: true, Status: model.PrinterStatusIdle, } if err := s.printerRepo.Create(ctx, printer); err != nil { return nil, "", err } return printer, token, nil } func (s *PrintService) PrinterHeartbeat(ctx context.Context, printerID int32, status int8, token string) error { // Verify token (in production, validate against stored token) // For now, just update heartbeat and status if err := s.printerRepo.UpdateHeartbeat(ctx, printerID); err != nil { return err } return s.printerRepo.UpdateStatus(ctx, printerID, status) } func (s *PrintService) SubmitPrintJob(ctx context.Context, orderItemID int32, printerID int32, filePath string, copies int, color, duplex bool, pageRange string) (*model.PrintJob, error) { jobNo := generateJobNo() job := &model.PrintJob{ JobNo: jobNo, PrinterID: printerID, OrderItemID: orderItemID, Status: model.PrintJobStatusPending, Progress: 0, } if err := s.printJobRepo.Create(ctx, job); err != nil { return nil, err } // Notify subscriber s.mu.RLock() ch, ok := s.subscribers[printerID] s.mu.RUnlock() if ok { select { case ch <- job: default: } } return job, nil } func (s *PrintService) CancelPrintJob(ctx context.Context, jobNo string) error { return s.printJobRepo.UpdateStatus(ctx, jobNo, model.PrintJobStatusCancelled, 0, "cancelled by user") } func (s *PrintService) SubscribeJobs(ctx context.Context, printerID int32, token string) (<-chan *model.PrintJob, error) { ch := make(chan *model.PrintJob, 100) s.mu.Lock() s.subscribers[printerID] = ch s.mu.Unlock() // Send pending jobs immediately go func() { jobs, err := s.printJobRepo.GetPendingJobs(ctx, printerID) if err != nil { return } for _, job := range jobs { select { case ch <- job: default: } } }() return ch, nil } func (s *PrintService) UnsubscribeJobs(printerID int32) { s.mu.Lock() defer s.mu.Unlock() if ch, ok := s.subscribers[printerID]; ok { close(ch) delete(s.subscribers, printerID) } } func (s *PrintService) ReportJobStatus(ctx context.Context, jobNo string, status string, progress int, errorMsg string) error { // Update job status if err := s.printJobRepo.UpdateStatus(ctx, jobNo, status, progress, errorMsg); err != nil { return err } // If completed, check if all jobs for the order are done if status == model.PrintJobStatusCompleted { job, _ := s.printJobRepo.GetByJobNo(ctx, jobNo) if job != nil { // Check if all jobs for the order are completed s.checkOrderCompletion(ctx, job.OrderItemID) } } return nil } func (s *PrintService) checkOrderCompletion(ctx context.Context, orderItemID int32) { // In production, check if all print jobs for the order are completed // and update order status accordingly } func (s *PrintService) checkPrinterHeartbeat() { ticker := time.NewTicker(30 * time.Second) for range ticker.C { // In production, check LastHeartbeat and mark printers as offline if no heartbeat // This is a placeholder for the heartbeat checker } } // ValidatePrinterToken validates the API key and returns the printer func (s *PrintService) ValidatePrinterToken(ctx context.Context, apiKey string) (*model.Printer, error) { return s.printerRepo.GetByAPIKey(ctx, apiKey) }