func (api *privetAPI) submitdoc(w http.ResponseWriter, r *http.Request) { log.Debugf("Received /submitdoc request: %+v", r) if ok := api.checkRequest(w, r, "POST"); !ok { return } file, err := ioutil.TempFile("", "cups-connector-privet-") if err != nil { log.Errorf("Failed to create file for new Privet job: %s", err) w.WriteHeader(http.StatusInternalServerError) return } defer file.Close() jobSize, err := io.Copy(file, r.Body) if err != nil { log.Errorf("Failed to copy new print job file: %s", err) w.WriteHeader(http.StatusInternalServerError) os.Remove(file.Name()) return } if length, err := strconv.ParseInt(r.Header.Get("Content-Length"), 10, 64); err != nil || length != jobSize { writeError(w, "invalid_params", "Content-Length header doesn't match length of content") os.Remove(file.Name()) return } jobType := r.Header.Get("Content-Type") if jobType == "" { writeError(w, "invalid_document_type", "Content-Type header is missing") os.Remove(file.Name()) return } printer, exists := api.getPrinter(api.name) if !exists { w.WriteHeader(http.StatusInternalServerError) os.Remove(file.Name()) return } if printer.State.State == cdd.CloudDeviceStateStopped { writeError(w, "printer_error", "Printer is stopped") os.Remove(file.Name()) return } jobName := r.Form.Get("job_name") userName := r.Form.Get("user_name") jobID := r.Form.Get("job_id") var expiresIn int32 var ticket *cdd.CloudJobTicket if jobID == "" { jobID, expiresIn = api.jc.createJob(nil) } else { var ok bool if expiresIn, ticket, ok = api.jc.getJobExpiresIn(jobID); !ok { pe := privetError{ Error: "invalid_print_job", Timeout: 5, }.json() w.Write(pe) os.Remove(file.Name()) return } } api.jobs <- &lib.Job{ CUPSPrinterName: api.name, Filename: file.Name(), Title: jobName, User: userName, JobID: jobID, Ticket: ticket, UpdateJob: api.jc.updateJob, } var response struct { JobID string `json:"job_id"` ExpiresIn int32 `json:"expires_in"` JobType string `json:"job_type"` JobSize int64 `json:"job_size"` JobName string `json:"job_name,omitempty"` } response.JobID = jobID response.ExpiresIn = expiresIn response.JobType = jobType response.JobSize = jobSize response.JobName = jobName j, err := json.MarshalIndent(response, "", " ") if err != nil { log.ErrorJobf(jobID, "Failed to marshal submitdoc response: %s", err) w.WriteHeader(http.StatusInternalServerError) return } w.Write(j) }
// printJob prints a new job to a CUPS printer, then polls the CUPS job state // and updates the GCP/Privet job state. then returns when the job state is DONE // or ABORTED. // // All errors are reported and logged from inside this function. func (pm *PrinterManager) printJob(cupsPrinterName, filename, title, user, jobID string, ticket *cdd.CloudJobTicket, updateJob func(string, cdd.PrintJobStateDiff) error) { defer os.Remove(filename) if !pm.addInFlightJob(jobID) { // This print job was already received. We probably received it // again because the first instance is still QUEUED (ie not // IN_PROGRESS). That's OK, just throw away the second instance. return } defer pm.deleteInFlightJob(jobID) if !pm.jobFullUsername { user = strings.Split(user, "@")[0] } printer, exists := pm.printers.GetByCUPSName(cupsPrinterName) if !exists { pm.incrementJobsProcessed(false) state := cdd.PrintJobStateDiff{ State: &cdd.JobState{ Type: cdd.JobStateAborted, ServiceActionCause: &cdd.ServiceActionCause{ErrorCode: cdd.ServiceActionCausePrinterDeleted}, }, } if err := updateJob(jobID, state); err != nil { log.ErrorJob(jobID, err) } return } cupsJobID, err := pm.cups.Print(&printer, filename, title, user, jobID, ticket) if err != nil { pm.incrementJobsProcessed(false) log.ErrorJobf(jobID, "Failed to submit to CUPS: %s", err) state := cdd.PrintJobStateDiff{ State: &cdd.JobState{ Type: cdd.JobStateAborted, DeviceActionCause: &cdd.DeviceActionCause{ErrorCode: cdd.DeviceActionCausePrintFailure}, }, } if err := updateJob(jobID, state); err != nil { log.ErrorJob(jobID, err) } return } log.InfoJobf(jobID, "Submitted as CUPS job %d", cupsJobID) var state cdd.PrintJobStateDiff ticker := time.NewTicker(time.Second) defer ticker.Stop() for _ = range ticker.C { cupsState, err := pm.cups.GetJobState(cupsJobID) if err != nil { log.WarningJobf(jobID, "Failed to get state of CUPS job %d: %s", cupsJobID, err) state = cdd.PrintJobStateDiff{ State: &cdd.JobState{ Type: cdd.JobStateAborted, DeviceActionCause: &cdd.DeviceActionCause{ErrorCode: cdd.DeviceActionCauseOther}, }, PagesPrinted: state.PagesPrinted, } if err := updateJob(jobID, state); err != nil { log.ErrorJob(jobID, err) } pm.incrementJobsProcessed(false) return } if !reflect.DeepEqual(cupsState, state) { state = cupsState if err = updateJob(jobID, state); err != nil { log.ErrorJob(jobID, err) } log.InfoJobf(jobID, "State: %s", state.State.Type) } if state.State.Type != cdd.JobStateInProgress { if state.State.Type == cdd.JobStateDone { pm.incrementJobsProcessed(true) } else { pm.incrementJobsProcessed(false) } return } } }