func TestConvertMarkers(t *testing.T) {
	log.SetLevel(log.ERROR)

	m, ms := convertMarkers(nil)
	if m != nil {
		t.Logf("expected nil")
		t.Fail()
	}
	if ms != nil {
		t.Logf("expected nil")
		t.Fail()
	}

	pt := map[string][]string{}
	m, ms = convertMarkers(pt)
	if m != nil {
		t.Logf("expected nil")
		t.Fail()
	}
	if ms != nil {
		t.Logf("expected nil")
		t.Fail()
	}

	pt = map[string][]string{
		attrMarkerNames:  []string{"black", "black", "black"},
		attrMarkerTypes:  []string{"toner", "toner", "ink"},
		attrMarkerLevels: []string{"10", "11", "12"},
	}
	m, ms = convertMarkers(pt)
	if m != nil {
		t.Logf("expected nil")
		t.Fail()
	}
	if ms != nil {
		t.Logf("expected nil")
		t.Fail()
	}

	pt = map[string][]string{
		attrMarkerNames:  []string{"black", "color"},
		attrMarkerTypes:  []string{"toner", "toner", "ink"},
		attrMarkerLevels: []string{"10", "11", "12"},
	}
	m, ms = convertMarkers(pt)
	if m != nil {
		t.Logf("expected nil")
		t.Fail()
	}
	if ms != nil {
		t.Logf("expected nil")
		t.Fail()
	}

	pt = map[string][]string{
		attrMarkerNames:  []string{"black", "color", "rainbow"},
		attrMarkerTypes:  []string{"toner", "toner"},
		attrMarkerLevels: []string{"10", "11", "12"},
	}
	m, ms = convertMarkers(pt)
	if m != nil {
		t.Logf("expected nil")
		t.Fail()
	}
	if ms != nil {
		t.Logf("expected nil")
		t.Fail()
	}

	pt = map[string][]string{
		attrMarkerNames:  []string{"black", " Reorder Part #12345", "color", "rainbow", "zebra", "pony"},
		attrMarkerTypes:  []string{"toner", "toner", "ink", "staples", "water", " Reorder H2O"},
		attrMarkerLevels: []string{"10", "11", "12", "208", "13"},
	}
	mExpected := &[]cdd.Marker{
		cdd.Marker{
			VendorID: "black, Reorder Part #12345",
			Type:     cdd.MarkerToner,
			Color:    &cdd.MarkerColor{Type: cdd.MarkerColorBlack},
		},
		cdd.Marker{
			VendorID: "color",
			Type:     cdd.MarkerToner,
			Color:    &cdd.MarkerColor{Type: cdd.MarkerColorColor},
		},
		cdd.Marker{
			VendorID: "rainbow",
			Type:     cdd.MarkerInk,
			Color: &cdd.MarkerColor{
				Type: cdd.MarkerColorCustom,
				CustomDisplayNameLocalized: cdd.NewLocalizedString("rainbow"),
			},
		},
		cdd.Marker{
			VendorID: "zebra",
			Type:     cdd.MarkerStaples,
		},
	}
	ten, eleven, twelve, eighty := int32(10), int32(11), int32(12), int32(80)
	msExpected := &cdd.MarkerState{
		Item: []cdd.MarkerStateItem{
			cdd.MarkerStateItem{
				VendorID:     "black, Reorder Part #12345",
				State:        cdd.MarkerStateExhausted,
				LevelPercent: &ten,
			},
			cdd.MarkerStateItem{
				VendorID:     "color",
				State:        cdd.MarkerStateOK,
				LevelPercent: &eleven,
			},
			cdd.MarkerStateItem{
				VendorID:     "rainbow",
				State:        cdd.MarkerStateOK,
				LevelPercent: &twelve,
			},
			cdd.MarkerStateItem{
				VendorID:     "zebra",
				State:        cdd.MarkerStateOK,
				LevelPercent: &eighty,
			},
		},
	}
	m, ms = convertMarkers(pt)
	if !reflect.DeepEqual(mExpected, m) {
		e, _ := json.Marshal(mExpected)
		f, _ := json.Marshal(m)
		t.Logf("expected\n %s\ngot\n %s", e, f)
		t.Fail()
	}
	if !reflect.DeepEqual(msExpected, ms) {
		e, _ := json.Marshal(msExpected)
		f, _ := json.Marshal(ms)
		t.Logf("expected\n %s\ngot\n %s", e, f)
		t.Fail()
	}
	pt = map[string][]string{
		attrMarkerNames:  []string{"black", "color", "rainbow", "zebra", "pony"},
		attrMarkerTypes:  []string{"toner", "toner", "ink", "staples", "water"},
		attrMarkerLevels: []string{"10", "11", "12", "208", "13"},
	}
	mExpected = &[]cdd.Marker{
		cdd.Marker{
			VendorID: "black",
			Type:     cdd.MarkerToner,
			Color:    &cdd.MarkerColor{Type: cdd.MarkerColorBlack},
		},
		cdd.Marker{
			VendorID: "color",
			Type:     cdd.MarkerToner,
			Color:    &cdd.MarkerColor{Type: cdd.MarkerColorColor},
		},
		cdd.Marker{
			VendorID: "rainbow",
			Type:     cdd.MarkerInk,
			Color: &cdd.MarkerColor{
				Type: cdd.MarkerColorCustom,
				CustomDisplayNameLocalized: cdd.NewLocalizedString("rainbow"),
			},
		},
		cdd.Marker{
			VendorID: "zebra",
			Type:     cdd.MarkerStaples,
		},
	}
	msExpected = &cdd.MarkerState{
		Item: []cdd.MarkerStateItem{
			cdd.MarkerStateItem{
				VendorID:     "black",
				State:        cdd.MarkerStateExhausted,
				LevelPercent: &ten,
			},
			cdd.MarkerStateItem{
				VendorID:     "color",
				State:        cdd.MarkerStateOK,
				LevelPercent: &eleven,
			},
			cdd.MarkerStateItem{
				VendorID:     "rainbow",
				State:        cdd.MarkerStateOK,
				LevelPercent: &twelve,
			},
			cdd.MarkerStateItem{
				VendorID:     "zebra",
				State:        cdd.MarkerStateOK,
				LevelPercent: &eighty,
			},
		},
	}
	m, ms = convertMarkers(pt)
	if !reflect.DeepEqual(mExpected, m) {
		e, _ := json.Marshal(mExpected)
		f, _ := json.Marshal(m)
		t.Logf("expected\n %s\ngot\n %s", e, f)
		t.Fail()
	}
	if !reflect.DeepEqual(msExpected, ms) {
		e, _ := json.Marshal(msExpected)
		f, _ := json.Marshal(ms)
		t.Logf("expected\n %s\ngot\n %s", e, f)
		t.Fail()
	}
}
func connector(context *cli.Context) int {
	config, configFilename, err := lib.GetConfig(context)
	if err != nil {
		fmt.Fprintf(os.Stderr, "Failed to read config file: %s", err)
		return 1
	}

	logFileMaxBytes := config.LogFileMaxMegabytes * 1024 * 1024
	var logWriter io.Writer
	logWriter, err = log.NewLogRoller(config.LogFileName, logFileMaxBytes, config.LogMaxFiles)
	if err != nil {
		fmt.Fprintf(os.Stderr, "Failed to start log roller: %s", err)
		return 1
	}

	if context.Bool("log-to-console") {
		logWriter = io.MultiWriter(logWriter, os.Stderr)
	}
	logLevel, ok := log.LevelFromString(config.LogLevel)
	if !ok {
		fmt.Fprintf(os.Stderr, "Log level %s is not recognized", config.LogLevel)
		return 1
	}
	log.SetLevel(logLevel)
	log.SetWriter(logWriter)

	if configFilename == "" {
		log.Info("No config file was found, so using defaults")
	}

	log.Error(lib.FullName)
	fmt.Println(lib.FullName)

	if !config.CloudPrintingEnable && !config.LocalPrintingEnable {
		log.Error("Cannot run connector with both local_printing_enable and cloud_printing_enable set to false")
		return 1
	}

	if _, err := os.Stat(config.MonitorSocketFilename); !os.IsNotExist(err) {
		if err != nil {
			log.Errorf("Failed to stat monitor socket: %s", err)
		} else {
			log.Errorf(
				"A connector is already running, or the monitoring socket %s wasn't cleaned up properly",
				config.MonitorSocketFilename)
		}
		return 1
	}

	jobs := make(chan *lib.Job, 10)
	xmppNotifications := make(chan xmpp.PrinterNotification, 5)

	var g *gcp.GoogleCloudPrint
	var x *xmpp.XMPP
	if config.CloudPrintingEnable {
		xmppPingTimeout, err := time.ParseDuration(config.XMPPPingTimeout)
		if err != nil {
			log.Fatalf("Failed to parse xmpp ping timeout: %s", err)
			return 1
		}
		xmppPingInterval, err := time.ParseDuration(config.XMPPPingInterval)
		if err != nil {
			log.Fatalf("Failed to parse xmpp ping interval default: %s", err)
			return 1
		}

		g, err = gcp.NewGoogleCloudPrint(config.GCPBaseURL, config.RobotRefreshToken,
			config.UserRefreshToken, config.ProxyName, config.GCPOAuthClientID,
			config.GCPOAuthClientSecret, config.GCPOAuthAuthURL, config.GCPOAuthTokenURL,
			config.GCPMaxConcurrentDownloads, jobs)
		if err != nil {
			log.Error(err)
			return 1
		}

		x, err = xmpp.NewXMPP(config.XMPPJID, config.ProxyName, config.XMPPServer, config.XMPPPort,
			xmppPingTimeout, xmppPingInterval, g.GetRobotAccessToken, xmppNotifications)
		if err != nil {
			log.Error(err)
			return 1
		}
		defer x.Quit()
	}

	cupsConnectTimeout, err := time.ParseDuration(config.CUPSConnectTimeout)
	if err != nil {
		log.Fatalf("Failed to parse CUPS connect timeout: %s", err)
		return 1
	}
	c, err := cups.NewCUPS(config.CopyPrinterInfoToDisplayName, config.PrefixJobIDToJobTitle,
		config.DisplayNamePrefix, config.CUPSPrinterAttributes, config.CUPSMaxConnections,
		cupsConnectTimeout)
	if err != nil {
		log.Fatal(err)
		return 1
	}
	defer c.Quit()

	var s *snmp.SNMPManager
	if config.SNMPEnable {
		log.Info("SNMP enabled")
		s, err = snmp.NewSNMPManager(config.SNMPCommunity, config.SNMPMaxConnections)
		if err != nil {
			log.Error(err)
			return 1
		}
		defer s.Quit()
	}

	var priv *privet.Privet
	if config.LocalPrintingEnable {
		if g == nil {
			priv, err = privet.NewPrivet(jobs, config.GCPBaseURL, nil)
		} else {
			priv, err = privet.NewPrivet(jobs, config.GCPBaseURL, g.ProximityToken)
		}
		if err != nil {
			log.Error(err)
			return 1
		}
		defer priv.Quit()
	}

	cupsPrinterPollInterval, err := time.ParseDuration(config.CUPSPrinterPollInterval)
	if err != nil {
		log.Fatalf("Failed to parse CUPS printer poll interval: %s", err)
		return 1
	}
	pm, err := manager.NewPrinterManager(c, g, priv, s, cupsPrinterPollInterval,
		config.CUPSJobQueueSize, config.CUPSJobFullUsername, config.CUPSIgnoreRawPrinters,
		config.ShareScope, jobs, xmppNotifications)
	if err != nil {
		log.Error(err)
		return 1
	}
	defer pm.Quit()

	m, err := monitor.NewMonitor(c, g, priv, pm, config.MonitorSocketFilename)
	if err != nil {
		log.Error(err)
		return 1
	}
	defer m.Quit()

	if config.CloudPrintingEnable {
		if config.LocalPrintingEnable {
			log.Errorf("Ready to rock as proxy '%s' and in local mode", config.ProxyName)
			fmt.Printf("Ready to rock as proxy '%s' and in local mode\n", config.ProxyName)
		} else {
			log.Errorf("Ready to rock as proxy '%s'", config.ProxyName)
			fmt.Printf("Ready to rock as proxy '%s'\n", config.ProxyName)
		}
	} else {
		log.Error("Ready to rock in local-only mode")
		fmt.Println("Ready to rock in local-only mode")
	}

	waitIndefinitely()

	log.Error("Shutting down")
	fmt.Println("")
	fmt.Println("Shutting down")

	return 0
}
func connector(context *cli.Context) int {
	config, configFilename, err := lib.GetConfig(context)
	if err != nil {
		fmt.Fprintf(os.Stderr, "Failed to read config file: %s", err)
		return 1
	}

	log.SetLogToConsole(context.Bool("log-to-console"))

	logLevel, ok := log.LevelFromString(config.LogLevel)
	if !ok {
		fmt.Fprintf(os.Stderr, "Log level %s is not recognized", config.LogLevel)
		return 1
	}
	log.SetLevel(logLevel)

	if configFilename == "" {
		log.Info("No config file was found, so using defaults")
	}

	log.Info(lib.FullName)
	fmt.Println(lib.FullName)

	if !config.CloudPrintingEnable && !config.LocalPrintingEnable {
		log.Fatal("Cannot run connector with both local_printing_enable and cloud_printing_enable set to false")
		return 1
	} else if config.LocalPrintingEnable {
		log.Fatal("Local printing has not been implemented in this version of the Windows connector.")
		return 1
	}

	jobs := make(chan *lib.Job, 10)
	xmppNotifications := make(chan xmpp.PrinterNotification, 5)

	var g *gcp.GoogleCloudPrint
	var x *xmpp.XMPP
	if config.CloudPrintingEnable {
		xmppPingTimeout, err := time.ParseDuration(config.XMPPPingTimeout)
		if err != nil {
			log.Fatalf("Failed to parse xmpp ping timeout: %s", err)
			return 1
		}
		xmppPingInterval, err := time.ParseDuration(config.XMPPPingInterval)
		if err != nil {
			log.Fatalf("Failed to parse xmpp ping interval default: %s", err)
			return 1
		}

		g, err = gcp.NewGoogleCloudPrint(config.GCPBaseURL, config.RobotRefreshToken,
			config.UserRefreshToken, config.ProxyName, config.GCPOAuthClientID,
			config.GCPOAuthClientSecret, config.GCPOAuthAuthURL, config.GCPOAuthTokenURL,
			config.GCPMaxConcurrentDownloads, jobs)
		if err != nil {
			log.Fatal(err)
			return 1
		}

		x, err = xmpp.NewXMPP(config.XMPPJID, config.ProxyName, config.XMPPServer, config.XMPPPort,
			xmppPingTimeout, xmppPingInterval, g.GetRobotAccessToken, xmppNotifications)
		if err != nil {
			log.Fatal(err)
			return 1
		}
		defer x.Quit()
	}

	ws, err := winspool.NewWinSpool(config.PrefixJobIDToJobTitle, config.DisplayNamePrefix, config.PrinterBlacklist)
	if err != nil {
		log.Fatal(err)
		return 1
	}

	nativePrinterPollInterval, err := time.ParseDuration(config.NativePrinterPollInterval)
	if err != nil {
		log.Fatalf("Failed to parse printer poll interval: %s", err)
		return 1
	}
	pm, err := manager.NewPrinterManager(ws, g, nil, nil, nativePrinterPollInterval,
		config.NativeJobQueueSize, false, false, config.ShareScope, jobs, xmppNotifications)
	if err != nil {
		log.Fatal(err)
		return 1
	}
	defer pm.Quit()

	if config.CloudPrintingEnable {
		if config.LocalPrintingEnable {
			log.Infof("Ready to rock as proxy '%s' and in local mode", config.ProxyName)
			fmt.Printf("Ready to rock as proxy '%s' and in local mode\n", config.ProxyName)
		} else {
			log.Infof("Ready to rock as proxy '%s'", config.ProxyName)
			fmt.Printf("Ready to rock as proxy '%s'\n", config.ProxyName)
		}
	} else {
		log.Info("Ready to rock in local-only mode")
		fmt.Println("Ready to rock in local-only mode")
	}

	waitIndefinitely()

	log.Info("Shutting down")
	fmt.Println("")
	fmt.Println("Shutting down")

	return 0
}