//export handleGroupStateChange
func handleGroupStateChange(group *C.AvahiEntryGroup, state C.AvahiEntryGroupState, name unsafe.Pointer) {
	switch state {
	case C.AVAHI_ENTRY_GROUP_COLLISION:
		log.Warningf("Avahi failed to register %s due to a naming collision", C.GoString((*C.char)(name)))
	case C.AVAHI_ENTRY_GROUP_FAILURE:
		log.Warningf("Avahi failed to register %s, don't know why", C.GoString((*C.char)(name)))
	}
}
func (api *privetAPI) createjob(w http.ResponseWriter, r *http.Request) {
	log.Debugf("Received /createjob request: %+v", r)
	if ok := api.checkRequest(w, r, "POST"); !ok {
		return
	}

	requestBody, err := ioutil.ReadAll(r.Body)
	if err != nil {
		log.Warningf("Failed to read request body: %s", err)
		writeError(w, "invalid_ticket", "Check connector logs")
		return
	}

	var ticket cdd.CloudJobTicket
	if err = json.Unmarshal(requestBody, &ticket); err != nil {
		log.Warningf("Failed to read request body: %s", err)
		writeError(w, "invalid_ticket", "Check connector logs")
		return
	}

	printer, exists := api.getPrinter(api.name)
	if !exists {
		w.WriteHeader(http.StatusInternalServerError)
		return
	}
	if printer.State.State == cdd.CloudDeviceStateStopped {
		writeError(w, "printer_error", "Printer is stopped")
		return
	}

	jobID, expiresIn := api.jc.createJob(&ticket)
	var response struct {
		JobID     string `json:"job_id"`
		ExpiresIn int32  `json:"expires_in"`
	}
	response.JobID = jobID
	response.ExpiresIn = expiresIn
	j, err := json.MarshalIndent(response, "", "  ")
	if err != nil {
		api.jc.deleteJob(jobID)
		log.Errorf("Failed to marrshal createJob response: %s", err)
		w.WriteHeader(http.StatusInternalServerError)
	} else {
		w.Write(j)
	}
}
// dispatchIncoming listens for new XMPP notifications and puts them into
// separate channels, by type of message.
func (x *internalXMPP) dispatchIncoming(dying chan<- struct{}) {
	for {
		// The xml.StartElement tells us what is coming up.
		startElement, err := readStartElement(x.xmlDecoder)
		if err != nil {
			if isXMLErrorClosedConnection(err) {
				break
			}
			log.Errorf("Failed to read the next start element: %s", err)
			break
		}

		// Parse the message.
		if startElement.Name.Local == "message" {
			var message struct {
				XMLName xml.Name `xml:"message"`
				Data    string   `xml:"push>data"`
			}

			if err := x.xmlDecoder.DecodeElement(&message, startElement); err != nil {
				if isXMLErrorClosedConnection(err) {
					break
				}
				log.Warningf("Error while parsing print jobs notification via XMPP: %s", err)
				continue
			}

			messageData, err := base64.StdEncoding.DecodeString(message.Data)
			if err != nil {
				log.Warningf("Failed to convert XMPP message data from base64: %s", err)
				continue
			}

			messageDataString := string(messageData)
			if strings.ContainsRune(messageDataString, '/') {
				if strings.HasSuffix(messageDataString, "/delete") {
					gcpID := strings.TrimSuffix(messageDataString, "/delete")
					x.notifications <- PrinterNotification{gcpID, PrinterDelete}
				}
				// Ignore other suffixes, like /update_settings.
			} else {
				x.notifications <- PrinterNotification{messageDataString, PrinterNewJobs}
			}

		} else if startElement.Name.Local == "iq" {
			var message struct {
				XMLName xml.Name `xml:"iq"`
				ID      string   `xml:"id,attr"`
				Type    string   `xml:"type,attr"`
			}

			if err := x.xmlDecoder.DecodeElement(&message, startElement); err != nil {
				if isXMLErrorClosedConnection(err) {
					break
				}
				log.Warningf("Error while parsing XMPP pong: %s", err)
				continue
			}

			pingID, err := strconv.ParseUint(message.ID, 10, 8)
			if err != nil {
				log.Warningf("Failed to convert XMPP ping ID: %s", err)
				continue
			}
			x.pongs <- uint8(pingID)

		} else {
			log.Warningf("Unexpected element while waiting for print message: %+v", startElement)
		}
	}

	dying <- struct{}{}
}
// convertMarkers converts CUPS marker-(names|types|levels) to *[]cdd.Marker and *cdd.MarkerState.
//
// Normalizes marker type: toner(Cartridge|-cartridge) => toner,
// ink(Cartridge|-cartridge|Ribbon|-ribbon) => ink
func convertMarkers(printerTags map[string][]string) (*[]cdd.Marker, *cdd.MarkerState) {
	names, types, levels := printerTags[attrMarkerNames], printerTags[attrMarkerTypes], printerTags[attrMarkerLevels]
	if len(names) == 0 || len(types) == 0 || len(levels) == 0 {
		return nil, nil
	}

	if len(names) != len(levels) {
		newNames := fixMarkers(names)
		if len(newNames) != len(levels) {
			log.Warningf("Received badly-formatted marker-names from CUPS: %s, %s, %s",
				strings.Join(names, ";"), strings.Join(types, ";"), strings.Join(levels, ";"))
			return nil, nil
		}
		names = newNames
	}

	{
		nameSet := make(map[string]struct{}, len(names))
		for _, name := range names {
			if _, exists := nameSet[name]; exists {
				return nil, nil
			}
			nameSet[name] = struct{}{}
		}
	}

	if len(types) != len(levels) {
		newTypes := fixMarkers(types)
		if len(newTypes) != len(levels) {
			log.Warningf("Received badly-formatted marker-types from CUPS: %s, %s, %s",
				strings.Join(names, ";"), strings.Join(types, ";"), strings.Join(levels, ";"))
			return nil, nil
		}
		types = newTypes
	}

	markers := make([]cdd.Marker, 0, len(names))
	states := cdd.MarkerState{make([]cdd.MarkerStateItem, 0, len(names))}
	for i := 0; i < len(names); i++ {
		if len(names[i]) == 0 {
			return nil, nil
		}
		var markerType cdd.MarkerType
		switch strings.ToLower(types[i]) {
		case "toner", "tonercartridge", "toner-cartridge":
			markerType = cdd.MarkerToner
		case "ink", "inkcartridge", "ink-cartridge", "ink-ribbon", "inkribbon":
			markerType = cdd.MarkerInk
		case "staples":
			markerType = cdd.MarkerStaples
		default:
			continue
		}

		var color *cdd.MarkerColor
		if markerType == cdd.MarkerToner || markerType == cdd.MarkerInk {
			nameStripped := strings.Replace(strings.Replace(strings.ToLower(names[i]), " ", "", -1), "-", "", -1)
			colorType := cdd.MarkerColorCustom
			for k, v := range cupsMarkerNameToGCP {
				if strings.HasPrefix(nameStripped, k) {
					colorType = v
					break
				}
			}
			color = &cdd.MarkerColor{Type: colorType}
			if colorType == cdd.MarkerColorCustom {
				name := names[i]
				name = strings.TrimSuffix(name, " Cartridge")
				name = strings.TrimSuffix(name, " cartridge")
				name = strings.TrimSuffix(name, " Ribbon")
				name = strings.TrimSuffix(name, " ribbon")
				name = strings.TrimSuffix(name, " Toner")
				name = strings.TrimSuffix(name, " toner")
				name = strings.TrimSuffix(name, " Ink")
				name = strings.TrimSuffix(name, " ink")
				name = strings.Replace(name, "-", " ", -1)
				color.CustomDisplayNameLocalized = cdd.NewLocalizedString(name)
			}
		}

		marker := cdd.Marker{
			VendorID: names[i],
			Type:     markerType,
			Color:    color,
		}

		level, err := strconv.ParseInt(levels[i], 10, 32)
		if err != nil {
			log.Warningf("Failed to parse CUPS marker state %s=%s: %s", names[i], levels[i], err)
			return nil, nil
		}
		if level > 100 {
			// Lop off extra (proprietary?) bits.
			level = level & 0x7f
		}
		if level < 0 || level > 100 {
			return nil, nil
		}

		var state cdd.MarkerStateType
		if level > 10 {
			state = cdd.MarkerStateOK
		} else {
			state = cdd.MarkerStateExhausted
		}
		level32 := int32(level)
		markerState := cdd.MarkerStateItem{
			VendorID:     names[i],
			State:        state,
			LevelPercent: &level32,
		}

		markers = append(markers, marker)
		states.Item = append(states.Item, markerState)
	}

	return &markers, &states
}