Files
backend/cloudprint-backend/internal/service/print_service.go

195 lines
5.0 KiB
Go
Raw Normal View History

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)
}