From 67f6c036fe1b3568d54566f9cc3166a081b73fa0 Mon Sep 17 00:00:00 2001 From: Jacob Marble Date: Wed, 24 Feb 2016 12:37:12 -0800 Subject: [PATCH 01/76] Read/write sparse config files. Replaced utility update-config-file with backfill-config-file and sparse-config-file. Utility init now creates a config file that is sparse, except for user-specified values that are != default values. Fixes #125 --- gcp-connector-util/gcp-cups-connector-util.go | 159 ++++------------- gcp-connector-util/init.go | 4 +- gcp-connector-util/main_unix.go | 115 ++---------- gcp-connector-util/main_windows.go | 18 +- gcp-cups-connector/gcp-cups-connector.go | 13 +- .../gcp-windows-connector.go | 5 +- lib/config.go | 141 ++++++++++++++- lib/config_unix.go | 164 +++++++++++++++--- lib/config_windows.go | 38 ++-- 9 files changed, 373 insertions(+), 284 deletions(-) diff --git a/gcp-connector-util/gcp-cups-connector-util.go b/gcp-connector-util/gcp-cups-connector-util.go index 8df710c..bd358a9 100644 --- a/gcp-connector-util/gcp-cups-connector-util.go +++ b/gcp-connector-util/gcp-cups-connector-util.go @@ -29,9 +29,14 @@ var commonCommands = []cli.Command{ Action: deleteAllGCPPrinters, }, cli.Command{ - Name: "update-config-file", - Usage: "Add new options to config file after update", - Action: updateConfigFile, + Name: "backfill-config-file", + Usage: "Add all keys, with default values, to the config file", + Action: backfillConfigFile, + }, + cli.Command{ + Name: "sparse-config-file", + Usage: "Remove all keys, with non-default values, from the config file", + Action: sparseConfigFile, }, cli.Command{ Name: "delete-gcp-job", @@ -176,128 +181,20 @@ func getGCP(config *lib.Config) *gcp.GoogleCloudPrint { return gcp } -// commonUpdateConfig updates the config object, with the help of configMap, -// which can indicate the absence of a value. -// Returns true if config was changed. -// -// Each platform should define a function updateConfig(*lib.Config, map[string]interface{}) -// which may call this function. -func commonUpdateConfig(config *lib.Config, configMap map[string]interface{}) bool { - dirty := false - - if _, exists := configMap["xmpp_server"]; !exists { - dirty = true - fmt.Println("Added xmpp_server") - config.XMPPServer = lib.DefaultConfig.XMPPServer - } - if _, exists := configMap["xmpp_port"]; !exists { - dirty = true - fmt.Println("Added xmpp_port") - config.XMPPPort = lib.DefaultConfig.XMPPPort - } - if _, exists := configMap["gcp_xmpp_ping_timeout"]; !exists { - dirty = true - fmt.Println("Added gcp_xmpp_ping_timeout") - config.XMPPPingTimeout = lib.DefaultConfig.XMPPPingTimeout - } - if _, exists := configMap["gcp_xmpp_ping_interval_default"]; !exists { - dirty = true - fmt.Println("Added gcp_xmpp_ping_interval_default") - config.XMPPPingInterval = lib.DefaultConfig.XMPPPingInterval - } - if _, exists := configMap["gcp_base_url"]; !exists { - dirty = true - fmt.Println("Added gcp_base_url") - config.GCPBaseURL = lib.DefaultConfig.GCPBaseURL - } - if _, exists := configMap["gcp_oauth_client_id"]; !exists { - dirty = true - fmt.Println("Added gcp_oauth_client_id") - config.GCPOAuthClientID = lib.DefaultConfig.GCPOAuthClientID - } - if _, exists := configMap["gcp_oauth_client_secret"]; !exists { - dirty = true - fmt.Println("Added gcp_oauth_client_secret") - config.GCPOAuthClientSecret = lib.DefaultConfig.GCPOAuthClientSecret - } - if _, exists := configMap["gcp_oauth_auth_url"]; !exists { - dirty = true - fmt.Println("Added gcp_oauth_auth_url") - config.GCPOAuthAuthURL = lib.DefaultConfig.GCPOAuthAuthURL - } - if _, exists := configMap["gcp_oauth_token_url"]; !exists { - dirty = true - fmt.Println("Added gcp_oauth_token_url") - config.GCPOAuthTokenURL = lib.DefaultConfig.GCPOAuthTokenURL - } - if _, exists := configMap["gcp_max_concurrent_downloads"]; !exists { - dirty = true - fmt.Println("Added gcp_max_concurrent_downloads") - config.GCPMaxConcurrentDownloads = lib.DefaultConfig.GCPMaxConcurrentDownloads - } - if _, exists := configMap["cups_job_queue_size"]; !exists { - dirty = true - fmt.Println("Added cups_job_queue_size") - config.NativeJobQueueSize = lib.DefaultConfig.NativeJobQueueSize - } - if _, exists := configMap["cups_printer_poll_interval"]; !exists { - dirty = true - fmt.Println("Added cups_printer_poll_interval") - config.NativePrinterPollInterval = lib.DefaultConfig.NativePrinterPollInterval - } - if _, exists := configMap["prefix_job_id_to_job_title"]; !exists { - dirty = true - fmt.Println("Added prefix_job_id_to_job_title") - config.PrefixJobIDToJobTitle = lib.DefaultConfig.PrefixJobIDToJobTitle - } - if _, exists := configMap["display_name_prefix"]; !exists { - dirty = true - fmt.Println("Added display_name_prefix") - config.DisplayNamePrefix = lib.DefaultConfig.DisplayNamePrefix - } - if _, exists := configMap["printer_blacklist"]; !exists { - dirty = true - fmt.Println("Added printer_blacklist") - config.PrinterBlacklist = lib.DefaultConfig.PrinterBlacklist - } - if _, exists := configMap["local_printing_enable"]; !exists { - dirty = true - fmt.Println("Added local_printing_enable") - config.LocalPrintingEnable = lib.DefaultConfig.LocalPrintingEnable - } - if _, exists := configMap["cloud_printing_enable"]; !exists { - dirty = true - _, robot_token_exists := configMap["robot_refresh_token"] - fmt.Println("Added cloud_printing_enable") - if robot_token_exists { - config.CloudPrintingEnable = true - } else { - config.CloudPrintingEnable = lib.DefaultConfig.CloudPrintingEnable - } - } - if _, exists := configMap["log_level"]; !exists { - dirty = true - fmt.Println("Added log_level") - config.LogLevel = lib.DefaultConfig.LogLevel - } - - return dirty -} - -// updateConfigFile opens the config file, adds any missing fields, -// writes the config file back. -func updateConfigFile(context *cli.Context) { - config, configFilename, err := lib.GetConfig(context) +// backfillConfigFile opens the config file, adds all missing keys +// and default values, then writes the config file back. +func backfillConfigFile(context *cli.Context) { + config, cfBefore, err := lib.GetConfig(context) if err != nil { log.Fatalln(err) } - if configFilename == "" { - fmt.Println("Could not find a config file to update") + if cfBefore == "" { + fmt.Println("Could not find a config file to backfill") return } // Same config in []byte format. - configRaw, err := ioutil.ReadFile(configFilename) + configRaw, err := ioutil.ReadFile(cfBefore) if err != nil { log.Fatalln(err) } @@ -308,13 +205,29 @@ func updateConfigFile(context *cli.Context) { log.Fatalln(err) } - dirty := updateConfig(config, configMap) + if cfWritten, err := config.Backfill(configMap).ToFile(context); err != nil { + fmt.Printf("Failed to write config file: %s\n", err) + } else { + fmt.Printf("Wrote %s\n", cfWritten) + } +} + +// sparseConfigFile opens the config file, removes most keys +// that have default values, then writes the config file back. +func sparseConfigFile(context *cli.Context) { + config, cfBefore, err := lib.GetConfig(context) + if err != nil { + log.Fatalln(err) + } + if cfBefore == "" { + fmt.Println("Could not find a config file to sparse") + return + } - if dirty { - config.ToFile(context) - fmt.Printf("Wrote %s\n", configFilename) + if cfWritten, err := config.Sparse(context).ToFile(context); err != nil { + fmt.Printf("Failed to write config file: %s\n", err) } else { - fmt.Println("Nothing to update") + fmt.Printf("Wrote %s\n", cfWritten) } } diff --git a/gcp-connector-util/init.go b/gcp-connector-util/init.go index c1c57a9..c91ffbc 100644 --- a/gcp-connector-util/init.go +++ b/gcp-connector-util/init.go @@ -100,7 +100,7 @@ var commonInitFlags = []cli.Flag{ }, cli.StringFlag{ Name: "log-level", - Usage: "Minimum event severity to log: PANIC, ERROR, WARN, INFO, DEBUG, VERBOSE", + Usage: "Minimum event severity to log: FATAL, ERROR, WARNING, INFO, DEBUG", Value: lib.DefaultConfig.LogLevel, }, } @@ -262,7 +262,7 @@ func createRobotAccount(context *cli.Context, userClient *http.Client) (string, } func writeConfigFile(context *cli.Context, config *lib.Config) string { - if configFilename, err := config.ToFile(context); err != nil { + if configFilename, err := config.Sparse(context).ToFile(context); err != nil { log.Fatalln(err) } else { return configFilename diff --git a/gcp-connector-util/main_unix.go b/gcp-connector-util/main_unix.go index c3a00c9..2c6c3e6 100644 --- a/gcp-connector-util/main_unix.go +++ b/gcp-connector-util/main_unix.go @@ -9,7 +9,6 @@ package main import ( - "fmt" "log" "os" "time" @@ -94,86 +93,6 @@ var unixCommands = []cli.Command{ }, } -func updateConfig(config *lib.Config, configMap map[string]interface{}) bool { - dirty := commonUpdateConfig(config, configMap) - - if _, exists := configMap["log_file_name"]; !exists { - dirty = true - fmt.Println("Added log_file_name") - config.LogFileName = lib.DefaultConfig.LogFileName - } - if _, exists := configMap["log_file_max_megabytes"]; !exists { - dirty = true - fmt.Println("Added log_file_max_megabytes") - config.LogFileMaxMegabytes = lib.DefaultConfig.LogFileMaxMegabytes - } - if _, exists := configMap["log_max_files"]; !exists { - dirty = true - fmt.Println("Added log_max_files") - config.LogMaxFiles = lib.DefaultConfig.LogMaxFiles - } - if _, exists := configMap["log_to_journal"]; !exists { - dirty = true - fmt.Println("Added log_to_journal") - config.LogToJournal = lib.DefaultConfig.LogToJournal - } - if _, exists := configMap["monitor_socket_filename"]; !exists { - dirty = true - fmt.Println("Added monitor_socket_filename") - config.MonitorSocketFilename = lib.DefaultConfig.MonitorSocketFilename - } - if _, exists := configMap["cups_max_connections"]; !exists { - dirty = true - fmt.Println("Added cups_max_connections") - config.CUPSMaxConnections = lib.DefaultConfig.CUPSMaxConnections - } - if _, exists := configMap["cups_connect_timeout"]; !exists { - dirty = true - fmt.Println("Added cups_connect_timeout") - config.CUPSConnectTimeout = lib.DefaultConfig.CUPSConnectTimeout - } - if _, exists := configMap["cups_printer_attributes"]; !exists { - dirty = true - fmt.Println("Added cups_printer_attributes") - config.CUPSPrinterAttributes = lib.DefaultConfig.CUPSPrinterAttributes - } else { - // Make sure all required attributes are present. - s := make(map[string]struct{}, len(config.CUPSPrinterAttributes)) - for _, a := range config.CUPSPrinterAttributes { - s[a] = struct{}{} - } - for _, a := range lib.DefaultConfig.CUPSPrinterAttributes { - if _, exists := s[a]; !exists { - dirty = true - fmt.Printf("Added %s to cups_printer_attributes\n", a) - config.CUPSPrinterAttributes = append(config.CUPSPrinterAttributes, a) - } - } - } - if _, exists := configMap["cups_job_full_username"]; !exists { - dirty = true - fmt.Println("Added cups_job_full_username") - config.CUPSJobFullUsername = lib.DefaultConfig.CUPSJobFullUsername - } - if _, exists := configMap["cups_ignore_raw_printers"]; !exists { - dirty = true - fmt.Println("Added cups_ignore_raw_printers") - config.CUPSIgnoreRawPrinters = lib.DefaultConfig.CUPSIgnoreRawPrinters - } - if _, exists := configMap["cups_ignore_class_printers"]; !exists { - dirty = true - fmt.Println("Added cups_ignore_class_printers") - config.CUPSIgnoreClassPrinters = lib.DefaultConfig.CUPSIgnoreClassPrinters - } - if _, exists := configMap["copy_printer_info_to_display_name"]; !exists { - dirty = true - fmt.Println("Added copy_printer_info_to_display_name") - config.CUPSCopyPrinterInfoToDisplayName = lib.DefaultConfig.CUPSCopyPrinterInfoToDisplayName - } - - return dirty -} - func main() { // Suppress date/time prefix. log.SetFlags(0) @@ -193,6 +112,9 @@ func main() { // createCloudConfig creates a config object that supports cloud and (optionally) local mode. func createCloudConfig(context *cli.Context, xmppJID, robotRefreshToken, userRefreshToken, shareScope, proxyName string, localEnable bool) *lib.Config { return &lib.Config{ + LocalPrintingEnable: localEnable, + CloudPrintingEnable: true, + XMPPJID: xmppJID, RobotRefreshToken: robotRefreshToken, UserRefreshToken: userRefreshToken, @@ -211,51 +133,50 @@ func createCloudConfig(context *cli.Context, xmppJID, robotRefreshToken, userRef NativeJobQueueSize: uint(context.Int("native-job-queue-size")), NativePrinterPollInterval: context.String("native-printer-poll-interval"), - PrefixJobIDToJobTitle: context.Bool("prefix-job-id-to-job-title"), + PrefixJobIDToJobTitle: lib.PointerToBool(context.Bool("prefix-job-id-to-job-title")), DisplayNamePrefix: context.String("display-name-prefix"), PrinterBlacklist: lib.DefaultConfig.PrinterBlacklist, - LocalPrintingEnable: localEnable, - CloudPrintingEnable: true, LogLevel: context.String("log-level"), LogFileName: context.String("log-file-name"), LogFileMaxMegabytes: uint(context.Int("log-file-max-megabytes")), LogMaxFiles: uint(context.Int("log-max-files")), - LogToJournal: context.Bool("log-to-journal"), + LogToJournal: lib.PointerToBool(context.Bool("log-to-journal")), MonitorSocketFilename: context.String("monitor-socket-filename"), CUPSMaxConnections: uint(context.Int("cups-max-connections")), CUPSConnectTimeout: context.String("cups-connect-timeout"), CUPSPrinterAttributes: lib.DefaultConfig.CUPSPrinterAttributes, - CUPSJobFullUsername: context.Bool("cups-job-full-username"), - CUPSIgnoreRawPrinters: context.Bool("cups-ignore-raw-printers"), - CUPSIgnoreClassPrinters: context.Bool("cups-ignore-class-printers"), - CUPSCopyPrinterInfoToDisplayName: context.Bool("cups-copy-printer-info-to-display-name"), + CUPSJobFullUsername: lib.PointerToBool(context.Bool("cups-job-full-username")), + CUPSIgnoreRawPrinters: lib.PointerToBool(context.Bool("cups-ignore-raw-printers")), + CUPSIgnoreClassPrinters: lib.PointerToBool(context.Bool("cups-ignore-class-printers")), + CUPSCopyPrinterInfoToDisplayName: lib.PointerToBool(context.Bool("copy-printer-info-to-display-name")), } } // createLocalConfig creates a config object that supports local mode. func createLocalConfig(context *cli.Context) *lib.Config { return &lib.Config{ + LocalPrintingEnable: true, + CloudPrintingEnable: false, + NativeJobQueueSize: uint(context.Int("native-job-queue-size")), NativePrinterPollInterval: context.String("native-printer-poll-interval"), - PrefixJobIDToJobTitle: context.Bool("prefix-job-id-to-job-title"), + PrefixJobIDToJobTitle: lib.PointerToBool(context.Bool("prefix-job-id-to-job-title")), DisplayNamePrefix: context.String("display-name-prefix"), PrinterBlacklist: lib.DefaultConfig.PrinterBlacklist, - LocalPrintingEnable: true, - CloudPrintingEnable: false, LogLevel: context.String("log-level"), LogFileName: context.String("log-file-name"), LogFileMaxMegabytes: uint(context.Int("log-file-max-megabytes")), LogMaxFiles: uint(context.Int("log-max-files")), - LogToJournal: context.Bool("log-to-journal"), + LogToJournal: lib.PointerToBool(context.Bool("log-to-journal")), MonitorSocketFilename: context.String("monitor-socket-filename"), CUPSMaxConnections: uint(context.Int("cups-max-connections")), CUPSConnectTimeout: context.String("cups-connect-timeout"), CUPSPrinterAttributes: lib.DefaultConfig.CUPSPrinterAttributes, - CUPSJobFullUsername: context.Bool("cups-job-full-username"), - CUPSIgnoreRawPrinters: context.Bool("cups-ignore-raw-printers"), - CUPSIgnoreClassPrinters: context.Bool("cups-ignore-class-printers"), - CUPSCopyPrinterInfoToDisplayName: context.Bool("cups-copy-printer-info-to-display-name"), + CUPSJobFullUsername: lib.PointerToBool(context.Bool("cups-job-full-username")), + CUPSIgnoreRawPrinters: lib.PointerToBool(context.Bool("cups-ignore-raw-printers")), + CUPSIgnoreClassPrinters: lib.PointerToBool(context.Bool("cups-ignore-class-printers")), + CUPSCopyPrinterInfoToDisplayName: lib.PointerToBool(context.Bool("copy-printer-info-to-display-name")), } } diff --git a/gcp-connector-util/main_windows.go b/gcp-connector-util/main_windows.go index d9046bc..fde19ac 100644 --- a/gcp-connector-util/main_windows.go +++ b/gcp-connector-util/main_windows.go @@ -61,10 +61,6 @@ var windowsCommands = []cli.Command{ }, } -func updateConfig(config *lib.Config, configMap map[string]interface{}) bool { - return commonUpdateConfig(config, configMap) -} - func installEventLog(c *cli.Context) { err := eventlog.InstallAsEventCreate(lib.ConnectorName, eventlog.Error|eventlog.Warning|eventlog.Info) if err != nil { @@ -204,6 +200,9 @@ func main() { // createCloudConfig creates a config object that supports cloud and (optionally) local mode. func createCloudConfig(context *cli.Context, xmppJID, robotRefreshToken, userRefreshToken, shareScope, proxyName string, localEnable bool) *lib.Config { return &lib.Config{ + LocalPrintingEnable: localEnable, + CloudPrintingEnable: true, + XMPPJID: xmppJID, RobotRefreshToken: robotRefreshToken, UserRefreshToken: userRefreshToken, @@ -222,11 +221,9 @@ func createCloudConfig(context *cli.Context, xmppJID, robotRefreshToken, userRef NativeJobQueueSize: uint(context.Int("native-job-queue-size")), NativePrinterPollInterval: context.String("native-printer-poll-interval"), - PrefixJobIDToJobTitle: context.Bool("prefix-job-id-to-job-title"), + PrefixJobIDToJobTitle: lib.PointerToBool(context.Bool("prefix-job-id-to-job-title")), DisplayNamePrefix: context.String("display-name-prefix"), PrinterBlacklist: lib.DefaultConfig.PrinterBlacklist, - LocalPrintingEnable: localEnable, - CloudPrintingEnable: true, LogLevel: context.String("log-level"), } } @@ -234,13 +231,14 @@ func createCloudConfig(context *cli.Context, xmppJID, robotRefreshToken, userRef // createLocalConfig creates a config object that supports local mode. func createLocalConfig(context *cli.Context) *lib.Config { return &lib.Config{ + LocalPrintingEnable: true, + CloudPrintingEnable: false, + NativeJobQueueSize: uint(context.Int("native-job-queue-size")), NativePrinterPollInterval: context.String("native-printer-poll-interval"), - PrefixJobIDToJobTitle: context.Bool("prefix-job-id-to-job-title"), + PrefixJobIDToJobTitle: lib.PointerToBool(context.Bool("prefix-job-id-to-job-title")), DisplayNamePrefix: context.String("display-name-prefix"), PrinterBlacklist: lib.DefaultConfig.PrinterBlacklist, - LocalPrintingEnable: true, - CloudPrintingEnable: false, LogLevel: context.String("log-level"), } } diff --git a/gcp-cups-connector/gcp-cups-connector.go b/gcp-cups-connector/gcp-cups-connector.go index 8215823..74f98b4 100644 --- a/gcp-cups-connector/gcp-cups-connector.go +++ b/gcp-cups-connector/gcp-cups-connector.go @@ -9,6 +9,7 @@ package main import ( + "encoding/json" "fmt" "io" "io/ioutil" @@ -54,7 +55,7 @@ func connector(context *cli.Context) int { return 1 } - logToJournal := config.LogToJournal && journal.Enabled() + logToJournal := *config.LogToJournal && journal.Enabled() logToConsole := context.Bool("log-to-console") if logToJournal { @@ -91,6 +92,8 @@ func connector(context *cli.Context) int { } else { log.Infof("Using config file %s", configFilename) } + completeConfig, _ := json.MarshalIndent(config, "", " ") + log.Debugf("Config: %s", string(completeConfig)) log.Info(lib.FullName) fmt.Println(lib.FullName) @@ -151,10 +154,10 @@ func connector(context *cli.Context) int { log.Fatalf("Failed to parse CUPS connect timeout: %s", err) return 1 } - c, err := cups.NewCUPS(config.CUPSCopyPrinterInfoToDisplayName, config.PrefixJobIDToJobTitle, + c, err := cups.NewCUPS(*config.CUPSCopyPrinterInfoToDisplayName, *config.PrefixJobIDToJobTitle, config.DisplayNamePrefix, config.CUPSPrinterAttributes, config.CUPSMaxConnections, - cupsConnectTimeout, config.PrinterBlacklist, config.CUPSIgnoreRawPrinters, - config.CUPSIgnoreClassPrinters) + cupsConnectTimeout, config.PrinterBlacklist, *config.CUPSIgnoreRawPrinters, + *config.CUPSIgnoreClassPrinters) if err != nil { log.Fatal(err) return 1 @@ -181,7 +184,7 @@ func connector(context *cli.Context) int { return 1 } pm, err := manager.NewPrinterManager(c, g, priv, nativePrinterPollInterval, - config.NativeJobQueueSize, config.CUPSJobFullUsername, config.ShareScope, + config.NativeJobQueueSize, *config.CUPSJobFullUsername, config.ShareScope, jobs, xmppNotifications) if err != nil { log.Fatal(err) diff --git a/gcp-windows-connector/gcp-windows-connector.go b/gcp-windows-connector/gcp-windows-connector.go index 90cc403..97528f1 100644 --- a/gcp-windows-connector/gcp-windows-connector.go +++ b/gcp-windows-connector/gcp-windows-connector.go @@ -9,6 +9,7 @@ package main import ( + "encoding/json" "fmt" "os" "time" @@ -100,6 +101,8 @@ func (service *service) Execute(args []string, r <-chan svc.ChangeRequest, s cha } else { log.Infof("Using config file %s", configFilename) } + completeConfig, _ := json.MarshalIndent(config, "", " ") + log.Debugf("Config: %s", string(completeConfig)) log.Info(lib.FullName) @@ -146,7 +149,7 @@ func (service *service) Execute(args []string, r <-chan svc.ChangeRequest, s cha defer x.Quit() } - ws, err := winspool.NewWinSpool(config.PrefixJobIDToJobTitle, config.DisplayNamePrefix, config.PrinterBlacklist) + ws, err := winspool.NewWinSpool(*config.PrefixJobIDToJobTitle, config.DisplayNamePrefix, config.PrinterBlacklist) if err != nil { log.Fatal(err) return false, 1 diff --git a/lib/config.go b/lib/config.go index 70ed1a4..4d989e6 100644 --- a/lib/config.go +++ b/lib/config.go @@ -12,6 +12,7 @@ import ( "encoding/json" "fmt" "io/ioutil" + "reflect" "runtime" "github.com/codegangsta/cli" @@ -42,6 +43,11 @@ var ( FullName = ConnectorName + " for " + platformName + " version " + BuildDate + "-" + runtime.GOOS ) +// PointerToBool converts a boolean value (constant) to a pointer-to-bool. +func PointerToBool(b bool) *bool { + return &b +} + // GetConfig reads a Config object from the config file indicated by the config // filename flag. If no such file exists, then DefaultConfig is returned. func GetConfig(context *cli.Context) (*Config, string, error) { @@ -50,16 +56,25 @@ func GetConfig(context *cli.Context) (*Config, string, error) { return &DefaultConfig, "", nil } - b, err := ioutil.ReadFile(cf) + configRaw, err := ioutil.ReadFile(cf) if err != nil { return nil, "", err } - var config Config - if err = json.Unmarshal(b, &config); err != nil { + config := new(Config) + if err = json.Unmarshal(configRaw, config); err != nil { + return nil, "", err + } + + // Same config as a map so that we can detect missing keys. + var configMap map[string]interface{} + if err = json.Unmarshal(configRaw, &configMap); err != nil { return nil, "", err } - return &config, cf, nil + + b := config.Backfill(configMap) + + return b, cf, nil } // ToFile writes this Config object to the config file indicated by ConfigFile. @@ -75,3 +90,121 @@ func (c *Config) ToFile(context *cli.Context) (string, error) { } return cf, nil } + +func (c *Config) commonSparse(context *cli.Context) *Config { + s := *c + + if s.XMPPServer == DefaultConfig.XMPPServer { + s.XMPPServer = "" + } + if !context.IsSet("xmpp-port") && + s.XMPPPort == DefaultConfig.XMPPPort { + s.XMPPPort = 0 + } + if !context.IsSet("xmpp-ping-timeout") && + s.XMPPPingTimeout == DefaultConfig.XMPPPingTimeout { + s.XMPPPingTimeout = "" + } + if !context.IsSet("xmpp-ping-interval") && + s.XMPPPingInterval == DefaultConfig.XMPPPingInterval { + s.XMPPPingInterval = "" + } + if s.GCPBaseURL == DefaultConfig.GCPBaseURL { + s.GCPBaseURL = "" + } + if s.GCPOAuthClientID == DefaultConfig.GCPOAuthClientID { + s.GCPOAuthClientID = "" + } + if s.GCPOAuthClientSecret == DefaultConfig.GCPOAuthClientSecret { + s.GCPOAuthClientSecret = "" + } + if s.GCPOAuthAuthURL == DefaultConfig.GCPOAuthAuthURL { + s.GCPOAuthAuthURL = "" + } + if s.GCPOAuthTokenURL == DefaultConfig.GCPOAuthTokenURL { + s.GCPOAuthTokenURL = "" + } + if !context.IsSet("gcp-max-concurrent-downloads") && + s.GCPMaxConcurrentDownloads == DefaultConfig.GCPMaxConcurrentDownloads { + s.GCPMaxConcurrentDownloads = 0 + } + if !context.IsSet("native-job-queue-size") && + s.NativeJobQueueSize == DefaultConfig.NativeJobQueueSize { + s.NativeJobQueueSize = 0 + } + if !context.IsSet("native-printer-poll-interval") && + s.NativePrinterPollInterval == DefaultConfig.NativePrinterPollInterval { + s.NativePrinterPollInterval = "" + } + if !context.IsSet("prefix-job-id-to-job-title") && + reflect.DeepEqual(s.PrefixJobIDToJobTitle, DefaultConfig.PrefixJobIDToJobTitle) { + s.PrefixJobIDToJobTitle = nil + } + if !context.IsSet("display-name-prefix") && + s.DisplayNamePrefix == DefaultConfig.DisplayNamePrefix { + s.DisplayNamePrefix = "" + } + + return &s +} + +func (c *Config) commonBackfill(configMap map[string]interface{}) *Config { + b := *c + + if _, exists := configMap["xmpp_server"]; !exists { + b.XMPPServer = DefaultConfig.XMPPServer + } + if _, exists := configMap["xmpp_port"]; !exists { + b.XMPPPort = DefaultConfig.XMPPPort + } + if _, exists := configMap["gcp_xmpp_ping_timeout"]; !exists { + b.XMPPPingTimeout = DefaultConfig.XMPPPingTimeout + } + if _, exists := configMap["gcp_xmpp_ping_interval_default"]; !exists { + b.XMPPPingInterval = DefaultConfig.XMPPPingInterval + } + if _, exists := configMap["gcp_base_url"]; !exists { + b.GCPBaseURL = DefaultConfig.GCPBaseURL + } + if _, exists := configMap["gcp_oauth_client_id"]; !exists { + b.GCPOAuthClientID = DefaultConfig.GCPOAuthClientID + } + if _, exists := configMap["gcp_oauth_client_secret"]; !exists { + b.GCPOAuthClientSecret = DefaultConfig.GCPOAuthClientSecret + } + if _, exists := configMap["gcp_oauth_auth_url"]; !exists { + b.GCPOAuthAuthURL = DefaultConfig.GCPOAuthAuthURL + } + if _, exists := configMap["gcp_oauth_token_url"]; !exists { + b.GCPOAuthTokenURL = DefaultConfig.GCPOAuthTokenURL + } + if _, exists := configMap["gcp_max_concurrent_downloads"]; !exists { + b.GCPMaxConcurrentDownloads = DefaultConfig.GCPMaxConcurrentDownloads + } + if _, exists := configMap["cups_job_queue_size"]; !exists { + b.NativeJobQueueSize = DefaultConfig.NativeJobQueueSize + } + if _, exists := configMap["cups_printer_poll_interval"]; !exists { + b.NativePrinterPollInterval = DefaultConfig.NativePrinterPollInterval + } + if _, exists := configMap["prefix_job_id_to_job_title"]; !exists { + b.PrefixJobIDToJobTitle = DefaultConfig.PrefixJobIDToJobTitle + } + if _, exists := configMap["display_name_prefix"]; !exists { + b.DisplayNamePrefix = DefaultConfig.DisplayNamePrefix + } + if _, exists := configMap["printer_blacklist"]; !exists { + b.PrinterBlacklist = DefaultConfig.PrinterBlacklist + } + if _, exists := configMap["local_printing_enable"]; !exists { + b.LocalPrintingEnable = DefaultConfig.LocalPrintingEnable + } + if _, exists := configMap["cloud_printing_enable"]; !exists { + b.CloudPrintingEnable = DefaultConfig.CloudPrintingEnable + } + if _, exists := configMap["log_level"]; !exists { + b.LogLevel = DefaultConfig.LogLevel + } + + return &b +} diff --git a/lib/config_unix.go b/lib/config_unix.go index e560c03..fcfc51c 100644 --- a/lib/config_unix.go +++ b/lib/config_unix.go @@ -11,6 +11,7 @@ package lib import ( "os" "path/filepath" + "reflect" "github.com/codegangsta/cli" "launchpad.net/go-xdg/v0" @@ -23,6 +24,12 @@ const ( ) type Config struct { + // Enable local discovery and printing. + LocalPrintingEnable bool `json:"local_printing_enable"` + + // Enable cloud discovery and printing. + CloudPrintingEnable bool `json:"cloud_printing_enable"` + // Associated with root account. XMPP credential. XMPPJID string `json:"xmpp_jid,omitempty"` @@ -71,28 +78,22 @@ type Config struct { // Maximum quantity of jobs (data) to download concurrently. GCPMaxConcurrentDownloads uint `json:"gcp_max_concurrent_downloads,omitempty"` - // CUPS job queue size. + // CUPS job queue size, must be greater than zero. // TODO: rename without cups_ prefix - NativeJobQueueSize uint `json:"cups_job_queue_size"` + NativeJobQueueSize uint `json:"cups_job_queue_size,omitempty"` // Interval (eg 10s, 1m) between CUPS printer state polls. // TODO: rename without cups_ prefix - NativePrinterPollInterval string `json:"cups_printer_poll_interval"` + NativePrinterPollInterval string `json:"cups_printer_poll_interval,omitempty"` // Add the job ID to the beginning of the job title. Useful for debugging. - PrefixJobIDToJobTitle bool `json:"prefix_job_id_to_job_title"` + PrefixJobIDToJobTitle *bool `json:"prefix_job_id_to_job_title,omitempty"` // Prefix for all GCP printers hosted by this connector. - DisplayNamePrefix string `json:"display_name_prefix"` + DisplayNamePrefix string `json:"display_name_prefix,omitempty"` // Ignore printers with native names. - PrinterBlacklist []string `json:"printer_blacklist"` - - // Enable local discovery and printing. - LocalPrintingEnable bool `json:"local_printing_enable"` - - // Enable cloud discovery and printing. - CloudPrintingEnable bool `json:"cloud_printing_enable"` + PrinterBlacklist []string `json:"printer_blacklist,omitempty"` // Least severity to log. LogLevel string `json:"log_level"` @@ -101,16 +102,16 @@ type Config struct { LogFileName string `json:"log_file_name"` // CUPS only: Maximum log file size. - LogFileMaxMegabytes uint `json:"log_file_max_megabytes"` + LogFileMaxMegabytes uint `json:"log_file_max_megabytes,omitempty"` // CUPS only: Maximum log file quantity. - LogMaxFiles uint `json:"log_max_files"` + LogMaxFiles uint `json:"log_max_files,omitempty"` // CUPS only: Log to the systemd journal instead of to files? - LogToJournal bool `json:"log_to_journal"` + LogToJournal *bool `json:"log_to_journal,omitempty"` // CUPS only: Filename of unix socket for connector-check to talk to connector. - MonitorSocketFilename string `json:"monitor_socket_filename"` + MonitorSocketFilename string `json:"monitor_socket_filename,omitempty"` // CUPS only: Maximum quantity of open CUPS connections. CUPSMaxConnections uint `json:"cups_max_connections,omitempty"` @@ -122,23 +123,26 @@ type Config struct { CUPSPrinterAttributes []string `json:"cups_printer_attributes,omitempty"` // CUPS only: use the full username (joe@example.com) in CUPS job. - CUPSJobFullUsername bool `json:"cups_job_full_username,omitempty"` + CUPSJobFullUsername *bool `json:"cups_job_full_username,omitempty"` // CUPS only: ignore printers with make/model 'Local Raw Printer'. - CUPSIgnoreRawPrinters bool `json:"cups_ignore_raw_printers"` + CUPSIgnoreRawPrinters *bool `json:"cups_ignore_raw_printers,omitempty"` // CUPS only: ignore printers with make/model 'Local Printer Class'. - CUPSIgnoreClassPrinters bool `json:"cups_ignore_class_printers"` + CUPSIgnoreClassPrinters *bool `json:"cups_ignore_class_printers,omitempty"` // CUPS only: copy the CUPS printer's printer-info attribute to the GCP printer's defaultDisplayName. // TODO: rename with cups_ prefix - CUPSCopyPrinterInfoToDisplayName bool `json:"copy_printer_info_to_display_name,omitempty"` + CUPSCopyPrinterInfoToDisplayName *bool `json:"copy_printer_info_to_display_name,omitempty"` } // DefaultConfig represents reasonable default values for Config fields. // Omitted Config fields are omitted on purpose; they are unique per // connector instance. var DefaultConfig = Config{ + LocalPrintingEnable: true, + CloudPrintingEnable: false, + XMPPServer: "talk.google.com", XMPPPort: 443, XMPPPingTimeout: "5s", @@ -152,17 +156,15 @@ var DefaultConfig = Config{ NativeJobQueueSize: 3, NativePrinterPollInterval: "1m", - PrefixJobIDToJobTitle: false, + PrefixJobIDToJobTitle: PointerToBool(false), DisplayNamePrefix: "", PrinterBlacklist: []string{}, - LocalPrintingEnable: true, - CloudPrintingEnable: false, LogLevel: "INFO", LogFileName: "/tmp/cups-connector", LogFileMaxMegabytes: 1, LogMaxFiles: 3, - LogToJournal: false, + LogToJournal: PointerToBool(false), MonitorSocketFilename: "/tmp/cups-connector-monitor.sock", @@ -192,10 +194,10 @@ var DefaultConfig = Config{ "orientation-requested-supported", "pdf-versions-supported", }, - CUPSJobFullUsername: false, - CUPSIgnoreRawPrinters: true, - CUPSIgnoreClassPrinters: true, - CUPSCopyPrinterInfoToDisplayName: true, + CUPSJobFullUsername: PointerToBool(false), + CUPSIgnoreRawPrinters: PointerToBool(true), + CUPSIgnoreClassPrinters: PointerToBool(true), + CUPSCopyPrinterInfoToDisplayName: PointerToBool(true), } // getConfigFilename gets the absolute filename of the config file specified by @@ -232,3 +234,109 @@ func getConfigFilename(context *cli.Context) (string, bool) { // it wasn't found anywhere else. return absCF, false } + +// Backfill returns a copy of this config with all missing keys set to default values. +func (c *Config) Backfill(configMap map[string]interface{}) *Config { + b := *c.commonBackfill(configMap) + + if _, exists := configMap["log_file_name"]; !exists { + b.LogFileName = DefaultConfig.LogFileName + } + if _, exists := configMap["log_file_max_megabytes"]; !exists { + b.LogFileMaxMegabytes = DefaultConfig.LogFileMaxMegabytes + } + if _, exists := configMap["log_max_files"]; !exists { + b.LogMaxFiles = DefaultConfig.LogMaxFiles + } + if _, exists := configMap["log_to_journal"]; !exists { + b.LogToJournal = DefaultConfig.LogToJournal + } + if _, exists := configMap["monitor_socket_filename"]; !exists { + b.MonitorSocketFilename = DefaultConfig.MonitorSocketFilename + } + if _, exists := configMap["cups_max_connections"]; !exists { + b.CUPSMaxConnections = DefaultConfig.CUPSMaxConnections + } + if _, exists := configMap["cups_connect_timeout"]; !exists { + b.CUPSConnectTimeout = DefaultConfig.CUPSConnectTimeout + } + if _, exists := configMap["cups_printer_attributes"]; !exists { + b.CUPSPrinterAttributes = DefaultConfig.CUPSPrinterAttributes + } else { + // Make sure all required attributes are present. + s := make(map[string]struct{}, len(b.CUPSPrinterAttributes)) + for _, a := range b.CUPSPrinterAttributes { + s[a] = struct{}{} + } + for _, a := range DefaultConfig.CUPSPrinterAttributes { + if _, exists := s[a]; !exists { + b.CUPSPrinterAttributes = append(b.CUPSPrinterAttributes, a) + } + } + } + if _, exists := configMap["cups_job_full_username"]; !exists { + b.CUPSJobFullUsername = DefaultConfig.CUPSJobFullUsername + } + if _, exists := configMap["cups_ignore_raw_printers"]; !exists { + b.CUPSIgnoreRawPrinters = DefaultConfig.CUPSIgnoreRawPrinters + } + if _, exists := configMap["cups_ignore_class_printers"]; !exists { + b.CUPSIgnoreClassPrinters = DefaultConfig.CUPSIgnoreClassPrinters + } + if _, exists := configMap["copy_printer_info_to_display_name"]; !exists { + b.CUPSCopyPrinterInfoToDisplayName = DefaultConfig.CUPSCopyPrinterInfoToDisplayName + } + + return &b +} + +// Sparse returns a copy of this config with obvious values removed. +func (c *Config) Sparse(context *cli.Context) *Config { + s := *c.commonSparse(context) + + if !context.IsSet("log-file-max-megabytes") && + s.LogFileMaxMegabytes == DefaultConfig.LogFileMaxMegabytes { + s.LogFileMaxMegabytes = 0 + } + if !context.IsSet("log-max-files") && + s.LogMaxFiles == DefaultConfig.LogMaxFiles { + s.LogMaxFiles = 0 + } + if !context.IsSet("log-to-journal") && + reflect.DeepEqual(s.LogToJournal, DefaultConfig.LogToJournal) { + s.LogToJournal = nil + } + if !context.IsSet("monitor-socket-filename") && + s.MonitorSocketFilename == DefaultConfig.MonitorSocketFilename { + s.MonitorSocketFilename = "" + } + if !context.IsSet("cups-max-connections") && + s.CUPSMaxConnections == DefaultConfig.CUPSMaxConnections { + s.CUPSMaxConnections = 0 + } + if !context.IsSet("cups-connect-timeout") && + s.CUPSConnectTimeout == DefaultConfig.CUPSConnectTimeout { + s.CUPSConnectTimeout = "" + } + if reflect.DeepEqual(s.CUPSPrinterAttributes, DefaultConfig.CUPSPrinterAttributes) { + s.CUPSPrinterAttributes = nil + } + if !context.IsSet("cups-job-full-username") && + reflect.DeepEqual(s.CUPSJobFullUsername, DefaultConfig.CUPSJobFullUsername) { + s.CUPSJobFullUsername = nil + } + if !context.IsSet("cups-ignore-raw-printers") && + reflect.DeepEqual(s.CUPSIgnoreRawPrinters, DefaultConfig.CUPSIgnoreRawPrinters) { + s.CUPSIgnoreRawPrinters = nil + } + if !context.IsSet("cups-ignore-class-printers") && + reflect.DeepEqual(s.CUPSIgnoreClassPrinters, DefaultConfig.CUPSIgnoreClassPrinters) { + s.CUPSIgnoreClassPrinters = nil + } + if !context.IsSet("copy-printer-info-to-display-name") && + reflect.DeepEqual(s.CUPSCopyPrinterInfoToDisplayName, DefaultConfig.CUPSCopyPrinterInfoToDisplayName) { + s.CUPSCopyPrinterInfoToDisplayName = nil + } + + return &s +} diff --git a/lib/config_windows.go b/lib/config_windows.go index dfd7805..badc1f6 100644 --- a/lib/config_windows.go +++ b/lib/config_windows.go @@ -22,6 +22,12 @@ const ( ) type Config struct { + // Enable local discovery and printing. + LocalPrintingEnable bool `json:"local_printing_enable"` + + // Enable cloud discovery and printing. + CloudPrintingEnable bool `json:"cloud_printing_enable"` + // Associated with root account. XMPP credential. XMPPJID string `json:"xmpp_jid,omitempty"` @@ -70,28 +76,22 @@ type Config struct { // Maximum quantity of jobs (data) to download concurrently. GCPMaxConcurrentDownloads uint `json:"gcp_max_concurrent_downloads,omitempty"` - // CUPS job queue size. + // Windows Spooler job queue size, must be greater than zero. // TODO: rename without cups_ prefix - NativeJobQueueSize uint `json:"cups_job_queue_size"` + NativeJobQueueSize uint `json:"cups_job_queue_size,omitempty"` - // Interval (eg 10s, 1m) between CUPS printer state polls. + // Interval (eg 10s, 1m) between Windows Spooler printer state polls. // TODO: rename without cups_ prefix - NativePrinterPollInterval string `json:"cups_printer_poll_interval"` + NativePrinterPollInterval string `json:"cups_printer_poll_interval,omitempty"` // Add the job ID to the beginning of the job title. Useful for debugging. - PrefixJobIDToJobTitle bool `json:"prefix_job_id_to_job_title"` + PrefixJobIDToJobTitle *bool `json:"prefix_job_id_to_job_title,omitempty"` // Prefix for all GCP printers hosted by this connector. - DisplayNamePrefix string `json:"display_name_prefix"` + DisplayNamePrefix string `json:"display_name_prefix,omitempty"` // Ignore printers with native names. - PrinterBlacklist []string `json:"printer_blacklist"` - - // Enable local discovery and printing. - LocalPrintingEnable bool `json:"local_printing_enable"` - - // Enable cloud discovery and printing. - CloudPrintingEnable bool `json:"cloud_printing_enable"` + PrinterBlacklist []string `json:"printer_blacklist,omitempty"` // Least severity to log. LogLevel string `json:"log_level"` @@ -114,7 +114,7 @@ var DefaultConfig = Config{ NativeJobQueueSize: 3, NativePrinterPollInterval: "1m", - PrefixJobIDToJobTitle: false, + PrefixJobIDToJobTitle: PointerToBool(false), DisplayNamePrefix: "", PrinterBlacklist: []string{ "Fax", @@ -162,3 +162,13 @@ func getConfigFilename(context *cli.Context) (string, bool) { // This is probably what the user expects if it wasn't found anywhere else. return absCF, false } + +// Backfill returns a copy of this config with all missing keys set to default values. +func (c *Config) Backfill(configMap map[string]interface{}) *Config { + return c.commonBackfill(configMap) +} + +// Sparse returns a copy of this config with obvious values removed. +func (c *Config) Sparse(context *cli.Context) *Config { + return c.commonSparse(context) +} From 46211b07c1ae61a2e39d006be2a087342fd070e6 Mon Sep 17 00:00:00 2001 From: Steve Wills Date: Wed, 9 Mar 2016 01:52:35 +0000 Subject: [PATCH 02/76] fix build on FreeBSD --- README.md | 2 +- cups/core.go | 4 +++- cups/cups.go | 2 +- cups/ppdcache.go | 2 +- cups/translate-attrs.go | 2 +- cups/translate-attrs_test.go | 2 +- cups/translate-ppd.go | 2 +- cups/translate-ppd_test.go | 2 +- cups/translate-ticket.go | 2 +- cups/translate-ticket_test.go | 2 +- gcp-connector-util/main_unix.go | 4 ++-- gcp-connector-util/monitor.go | 2 +- gcp-cups-connector/gcp-cups-connector.go | 4 ++-- lib/config_unix.go | 2 +- log/log_unix.go | 2 +- log/logroller.go | 2 +- monitor/monitor.go | 2 +- privet/avahi.c | 2 +- privet/avahi.go | 5 +++-- privet/avahi.h | 2 +- winspool/cairo.go | 2 ++ winspool/poppler.go | 2 ++ winspool/utf16.go | 2 ++ winspool/win32.go | 2 ++ winspool/winspool.go | 2 ++ 25 files changed, 36 insertions(+), 23 deletions(-) diff --git a/README.md b/README.md index 0234dc9..69f353d 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # Google Cloud Print Connector ## Introduction -Share printers from your Windows, Linux, or OS X computer with ChromeOS and Android devices, using the Cloud Print Connector. The Connector is a purpose-built system process. It can share hundreds of printers on a powerful server, or one printer on a Raspberry Pi. +Share printers from your Windows, Linux, FreeBSD or OS X computer with ChromeOS and Android devices, using the Cloud Print Connector. The Connector is a purpose-built system process. It can share hundreds of printers on a powerful server, or one printer on a Raspberry Pi. Lots of help can be found in [the wiki](https://github.com/google/cups-connector/wiki). diff --git a/cups/core.go b/cups/core.go index 3fd8208..e9af80b 100644 --- a/cups/core.go +++ b/cups/core.go @@ -4,11 +4,13 @@ // license that can be found in the LICENSE file or at // https://developers.google.com/open-source/licenses/bsd -// +build linux darwin +// +build linux darwin freebsd package cups /* +#cgo CFLAGS: -I/usr/local/include +#cgo LDFLAGS: -L/usr/local/lib #include "cups.h" */ import "C" diff --git a/cups/cups.go b/cups/cups.go index d33ddb6..52d2f55 100644 --- a/cups/cups.go +++ b/cups/cups.go @@ -4,7 +4,7 @@ // license that can be found in the LICENSE file or at // https://developers.google.com/open-source/licenses/bsd -// +build linux darwin +// +build linux darwin freebsd package cups diff --git a/cups/ppdcache.go b/cups/ppdcache.go index bebb332..c801f34 100644 --- a/cups/ppdcache.go +++ b/cups/ppdcache.go @@ -4,7 +4,7 @@ // license that can be found in the LICENSE file or at // https://developers.google.com/open-source/licenses/bsd -// +build linux darwin +// +build linux darwin freebsd package cups diff --git a/cups/translate-attrs.go b/cups/translate-attrs.go index 59b5055..8296c8c 100644 --- a/cups/translate-attrs.go +++ b/cups/translate-attrs.go @@ -4,7 +4,7 @@ // license that can be found in the LICENSE file or at // https://developers.google.com/open-source/licenses/bsd -// +build linux darwin +// +build linux darwin freebsd package cups diff --git a/cups/translate-attrs_test.go b/cups/translate-attrs_test.go index 3781e54..34fa172 100644 --- a/cups/translate-attrs_test.go +++ b/cups/translate-attrs_test.go @@ -4,7 +4,7 @@ // license that can be found in the LICENSE file or at // https://developers.google.com/open-source/licenses/bsd -// +build linux darwin +// +build linux darwin freebsd package cups diff --git a/cups/translate-ppd.go b/cups/translate-ppd.go index 40c0e3c..417969a 100644 --- a/cups/translate-ppd.go +++ b/cups/translate-ppd.go @@ -4,7 +4,7 @@ // license that can be found in the LICENSE file or at // https://developers.google.com/open-source/licenses/bsd -// +build linux darwin +// +build linux darwin freebsd package cups diff --git a/cups/translate-ppd_test.go b/cups/translate-ppd_test.go index 4b1968d..c5ccbe7 100644 --- a/cups/translate-ppd_test.go +++ b/cups/translate-ppd_test.go @@ -4,7 +4,7 @@ // license that can be found in the LICENSE file or at // https://developers.google.com/open-source/licenses/bsd -// +build linux darwin +// +build linux darwin freebsd package cups diff --git a/cups/translate-ticket.go b/cups/translate-ticket.go index 6e7e696..c815c0a 100644 --- a/cups/translate-ticket.go +++ b/cups/translate-ticket.go @@ -4,7 +4,7 @@ // license that can be found in the LICENSE file or at // https://developers.google.com/open-source/licenses/bsd -// +build linux darwin +// +build linux darwin freebsd package cups diff --git a/cups/translate-ticket_test.go b/cups/translate-ticket_test.go index 6afebcf..d27c891 100644 --- a/cups/translate-ticket_test.go +++ b/cups/translate-ticket_test.go @@ -4,7 +4,7 @@ // license that can be found in the LICENSE file or at // https://developers.google.com/open-source/licenses/bsd -// +build linux darwin +// +build linux darwin freebsd package cups diff --git a/gcp-connector-util/main_unix.go b/gcp-connector-util/main_unix.go index 2c6c3e6..2213a28 100644 --- a/gcp-connector-util/main_unix.go +++ b/gcp-connector-util/main_unix.go @@ -1,11 +1,11 @@ +// +build linux darwin freebsd + // Copyright 2015 Google Inc. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file or at // https://developers.google.com/open-source/licenses/bsd -// +build linux darwin - package main import ( diff --git a/gcp-connector-util/monitor.go b/gcp-connector-util/monitor.go index aa14feb..08754b1 100644 --- a/gcp-connector-util/monitor.go +++ b/gcp-connector-util/monitor.go @@ -4,7 +4,7 @@ // license that can be found in the LICENSE file or at // https://developers.google.com/open-source/licenses/bsd -// +build linux darwin +// +build linux darwin freebsd package main diff --git a/gcp-cups-connector/gcp-cups-connector.go b/gcp-cups-connector/gcp-cups-connector.go index 74f98b4..88e3bce 100644 --- a/gcp-cups-connector/gcp-cups-connector.go +++ b/gcp-cups-connector/gcp-cups-connector.go @@ -1,11 +1,11 @@ +// +build linux darwin freebsd + // Copyright 2015 Google Inc. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file or at // https://developers.google.com/open-source/licenses/bsd -// +build linux darwin - package main import ( diff --git a/lib/config_unix.go b/lib/config_unix.go index fcfc51c..50271bc 100644 --- a/lib/config_unix.go +++ b/lib/config_unix.go @@ -4,7 +4,7 @@ // license that can be found in the LICENSE file or at // https://developers.google.com/open-source/licenses/bsd -// +build linux darwin +// +build linux darwin freebsd package lib diff --git a/log/log_unix.go b/log/log_unix.go index 5211b73..7b759ad 100644 --- a/log/log_unix.go +++ b/log/log_unix.go @@ -4,7 +4,7 @@ // license that can be found in the LICENSE file or at // https://developers.google.com/open-source/licenses/bsd -// +build linux darwin +// +build linux darwin freebsd // The log package logs to an io.Writer using the same log format that CUPS uses. package log diff --git a/log/logroller.go b/log/logroller.go index 972ee31..fe45af8 100644 --- a/log/logroller.go +++ b/log/logroller.go @@ -4,7 +4,7 @@ // license that can be found in the LICENSE file or at // https://developers.google.com/open-source/licenses/bsd -// +build linux darwin +// +build linux darwin freebsd package log diff --git a/monitor/monitor.go b/monitor/monitor.go index bad9ede..ca043d7 100644 --- a/monitor/monitor.go +++ b/monitor/monitor.go @@ -4,7 +4,7 @@ // license that can be found in the LICENSE file or at // https://developers.google.com/open-source/licenses/bsd -// +build linux darwin +// +build linux darwin freebsd package monitor diff --git a/privet/avahi.c b/privet/avahi.c index c399881..d69784b 100644 --- a/privet/avahi.c +++ b/privet/avahi.c @@ -4,7 +4,7 @@ // license that can be found in the LICENSE file or at // https://developers.google.com/open-source/licenses/bsd -// +build linux +// +build linux freebsd #include "avahi.h" #include "_cgo_export.h" diff --git a/privet/avahi.go b/privet/avahi.go index 6dc741d..2370d21 100644 --- a/privet/avahi.go +++ b/privet/avahi.go @@ -4,11 +4,12 @@ // license that can be found in the LICENSE file or at // https://developers.google.com/open-source/licenses/bsd -// +build linux +// +build linux freebsd package privet -// #cgo LDFLAGS: -lavahi-client -lavahi-common +// #cgo CFLAGS: -I%%PREFIX%%/include +// #cgo LDFLAGS: -L%%PREFIX%%/lib -lavahi-client -lavahi-common // #include "avahi.h" import "C" import ( diff --git a/privet/avahi.h b/privet/avahi.h index c8aa622..a1d2bae 100644 --- a/privet/avahi.h +++ b/privet/avahi.h @@ -4,7 +4,7 @@ // license that can be found in the LICENSE file or at // https://developers.google.com/open-source/licenses/bsd -// +build linux +// +build linux freebsd #include #include diff --git a/winspool/cairo.go b/winspool/cairo.go index 030b84b..8f8447d 100644 --- a/winspool/cairo.go +++ b/winspool/cairo.go @@ -1,3 +1,5 @@ +// +build linux darwin + /* Copyright 2015 Google Inc. All rights reserved. diff --git a/winspool/poppler.go b/winspool/poppler.go index 2a5cc9a..9e0973a 100644 --- a/winspool/poppler.go +++ b/winspool/poppler.go @@ -1,3 +1,5 @@ +// +build linux darwin + /* Copyright 2015 Google Inc. All rights reserved. diff --git a/winspool/utf16.go b/winspool/utf16.go index 4757ca0..9f70145 100644 --- a/winspool/utf16.go +++ b/winspool/utf16.go @@ -1,3 +1,5 @@ +// +build linux darwin + /* Copyright 2015 Google Inc. All rights reserved. diff --git a/winspool/win32.go b/winspool/win32.go index ab44015..f408bdb 100644 --- a/winspool/win32.go +++ b/winspool/win32.go @@ -1,3 +1,5 @@ +// +build linux darwin + /* Copyright 2015 Google Inc. All rights reserved. diff --git a/winspool/winspool.go b/winspool/winspool.go index c8ce290..35ca4bc 100644 --- a/winspool/winspool.go +++ b/winspool/winspool.go @@ -1,3 +1,5 @@ +// +build linux darwin + /* Copyright 2015 Google Inc. All rights reserved. From 085313bd5325ef3db26308e14003ba90efaefb45 Mon Sep 17 00:00:00 2001 From: Jacob Marble Date: Wed, 9 Mar 2016 10:10:49 -0800 Subject: [PATCH 03/76] windows: Add build tags to winspool directory. This fixes build errors when building on non-Windows with go get github.com/google/cups-connector/... --- winspool/cairo.go | 12 ++++++------ winspool/poppler.go | 12 ++++++------ winspool/utf16.go | 12 ++++++------ winspool/win32.go | 12 ++++++------ winspool/winspool.go | 12 ++++++------ 5 files changed, 30 insertions(+), 30 deletions(-) diff --git a/winspool/cairo.go b/winspool/cairo.go index 030b84b..d932f35 100644 --- a/winspool/cairo.go +++ b/winspool/cairo.go @@ -1,10 +1,10 @@ -/* -Copyright 2015 Google Inc. All rights reserved. +// Copyright 2015 Google Inc. All rights reserved. -Use of this source code is governed by a BSD-style -license that can be found in the LICENSE file or at -https://developers.google.com/open-source/licenses/bsd -*/ +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file or at +// https://developers.google.com/open-source/licenses/bsd + +// +build windows package winspool diff --git a/winspool/poppler.go b/winspool/poppler.go index 2a5cc9a..05ab64d 100644 --- a/winspool/poppler.go +++ b/winspool/poppler.go @@ -1,10 +1,10 @@ -/* -Copyright 2015 Google Inc. All rights reserved. +// Copyright 2015 Google Inc. All rights reserved. -Use of this source code is governed by a BSD-style -license that can be found in the LICENSE file or at -https://developers.google.com/open-source/licenses/bsd -*/ +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file or at +// https://developers.google.com/open-source/licenses/bsd + +// +build windows package winspool diff --git a/winspool/utf16.go b/winspool/utf16.go index 4757ca0..41d9cc7 100644 --- a/winspool/utf16.go +++ b/winspool/utf16.go @@ -1,10 +1,10 @@ -/* -Copyright 2015 Google Inc. All rights reserved. +// Copyright 2015 Google Inc. All rights reserved. -Use of this source code is governed by a BSD-style -license that can be found in the LICENSE file or at -https://developers.google.com/open-source/licenses/bsd -*/ +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file or at +// https://developers.google.com/open-source/licenses/bsd + +// +build windows package winspool diff --git a/winspool/win32.go b/winspool/win32.go index ab44015..0669c4c 100644 --- a/winspool/win32.go +++ b/winspool/win32.go @@ -1,10 +1,10 @@ -/* -Copyright 2015 Google Inc. All rights reserved. +// Copyright 2015 Google Inc. All rights reserved. -Use of this source code is governed by a BSD-style -license that can be found in the LICENSE file or at -https://developers.google.com/open-source/licenses/bsd -*/ +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file or at +// https://developers.google.com/open-source/licenses/bsd + +// +build windows package winspool diff --git a/winspool/winspool.go b/winspool/winspool.go index c8ce290..f28eeb9 100644 --- a/winspool/winspool.go +++ b/winspool/winspool.go @@ -1,10 +1,10 @@ -/* -Copyright 2015 Google Inc. All rights reserved. +// Copyright 2015 Google Inc. All rights reserved. -Use of this source code is governed by a BSD-style -license that can be found in the LICENSE file or at -https://developers.google.com/open-source/licenses/bsd -*/ +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file or at +// https://developers.google.com/open-source/licenses/bsd + +// +build windows package winspool From bcf210aaea6e1c8b6e75b92e1e40bbf751600a07 Mon Sep 17 00:00:00 2001 From: Steve Wills Date: Wed, 9 Mar 2016 19:11:34 +0000 Subject: [PATCH 04/76] incorporate comments from review --- cups/core.go | 4 ++-- gcp-connector-util/main_unix.go | 4 ++-- gcp-cups-connector/gcp-cups-connector.go | 4 ++-- privet/avahi.go | 5 +++-- 4 files changed, 9 insertions(+), 8 deletions(-) diff --git a/cups/core.go b/cups/core.go index e9af80b..0e04cdc 100644 --- a/cups/core.go +++ b/cups/core.go @@ -9,8 +9,8 @@ package cups /* -#cgo CFLAGS: -I/usr/local/include -#cgo LDFLAGS: -L/usr/local/lib +#cgo freebsd CFLAGS: -I/usr/local/include +#cgo freebsd LDFLAGS: -L/usr/local/lib #include "cups.h" */ import "C" diff --git a/gcp-connector-util/main_unix.go b/gcp-connector-util/main_unix.go index 2213a28..be24091 100644 --- a/gcp-connector-util/main_unix.go +++ b/gcp-connector-util/main_unix.go @@ -1,11 +1,11 @@ -// +build linux darwin freebsd - // Copyright 2015 Google Inc. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file or at // https://developers.google.com/open-source/licenses/bsd +// +build linux darwin freebsd + package main import ( diff --git a/gcp-cups-connector/gcp-cups-connector.go b/gcp-cups-connector/gcp-cups-connector.go index 88e3bce..c8ae4df 100644 --- a/gcp-cups-connector/gcp-cups-connector.go +++ b/gcp-cups-connector/gcp-cups-connector.go @@ -1,11 +1,11 @@ -// +build linux darwin freebsd - // Copyright 2015 Google Inc. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file or at // https://developers.google.com/open-source/licenses/bsd +// +build linux darwin freebsd + package main import ( diff --git a/privet/avahi.go b/privet/avahi.go index 2370d21..cbb8dfa 100644 --- a/privet/avahi.go +++ b/privet/avahi.go @@ -8,8 +8,9 @@ package privet -// #cgo CFLAGS: -I%%PREFIX%%/include -// #cgo LDFLAGS: -L%%PREFIX%%/lib -lavahi-client -lavahi-common +// #cgo linux LDFLAGS: -lavahi-client -lavahi-common +// #cgo freebsd CFLAGS: -I/usr/local/include +// #cgo freebsd LDFLAGS: -L/usr/local/lib -lavahi-client -lavahi-common // #include "avahi.h" import "C" import ( From 3466cd79445118e75b178508018cfef341ce28d6 Mon Sep 17 00:00:00 2001 From: arastooz Date: Mon, 22 Feb 2016 14:35:54 -0800 Subject: [PATCH 05/76] Initial version of Windows installer Installs binaries and dependencies Displays license agreement English x64 only Installs service Service does not start currently when installed Note: RTF version of license agreement is necessary for Windows Installer Tested: all the actions in various install states, but not with actual GCP binaries --- wix/LICENSE.rtf | Bin 0 -> 1771 bytes wix/README.md | 33 +++++++ wix/windows-connector.wxs | 199 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 232 insertions(+) create mode 100644 wix/LICENSE.rtf create mode 100644 wix/README.md create mode 100644 wix/windows-connector.wxs diff --git a/wix/LICENSE.rtf b/wix/LICENSE.rtf new file mode 100644 index 0000000000000000000000000000000000000000..c1218753f7a4554cb6065da4b9c9e46b6653ba04 GIT binary patch literal 1771 zcmbtV%WmT~6zw`d{^4R)oq>_snMqM}R z;-Fm==*E`#IQP5^zZT9nv!XP;9zLqWes+6zThyv)riHObUD@tX`q!ed#`}jBAErfP zbXzplW9fS3rx4kr)5<}rp1;4nU0=NxUy8jl%9Y+a;JSKL_3ad9)0^qdYd^gz6mfR*!2}xweG#s4@a-9fzs4)>=kGOJ@T#8u=1cy>0Y3*uInf8totweSa2c#x7acc0SWW%M}wKvW46dP63}cJiBKxjbXk6xuOtdn;mD%m4sE6#FPslX^g9>g3+gqW|Sp9tj2sEKejakKs`4(y0XRx*?^@ge_?r(ZDMU zP+KPGXAF`d$7R3`)^7<8#v +to the light.exe command line to specify where the files can be found. + +If mingw64 is not installed to C:\msys64\mingw64, then use -dDependencyDir= +to specify where it is installed. + +## Installation Instructions +Install the MSI by any normal method of installing an MSI file (double-clicking, automated deployment, etc.) + +During an installation with UI, gcp-connector-util init will be run as the last step which +will open a console window to initialize the connector. + +The following public properties may be set during install of the MSI +(see https://msdn.microsoft.com/en-us/library/windows/desktop/aa370912(v=vs.85).aspx) +* INITCMD = Command line to use to run gcp-connector-util.exe as a silent init during the install +* NO_INSTALL_SERVICE = Set to "yes" to skip installing the service during the install +* NO_START_SERVICE = Set to "yes" to skip starting the service during the install diff --git a/wix/windows-connector.wxs b/wix/windows-connector.wxs new file mode 100644 index 0000000..58f6ea3 --- /dev/null +++ b/wix/windows-connector.wxs @@ -0,0 +1,199 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + NOT INITCMD + + + + + + INITCMD + NOT NO_INSTALL_SERVICE AND NOT Installed + $CUPS_Connector_exe=3 AND NOT (NO_INSTALL_SERVICE OR NO_START_SERVICE) + ?CUPS_Connector_exe=3 + $CUPS_Connector_exe=2 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 7560bbef93996a671023163caf65afa0707a8656 Mon Sep 17 00:00:00 2001 From: Steve Wills Date: Wed, 9 Mar 2016 19:21:42 +0000 Subject: [PATCH 06/76] add travis support --- .travis.yml | 16 ++++++++++++++++ README.md | 5 +++++ 2 files changed, 21 insertions(+) create mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..e350b74 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,16 @@ +language: go + +go: + - 1.5 + - 1.6 + - tip + +os: + - linux + - osx + +before_install: + - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then sudo apt-get -qq update ; fi + - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then sudo apt-get install -y libcups2-dev libavahi-client-dev ; fi + - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew update ; fi + - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew install bazaar ; fi diff --git a/README.md b/README.md index 69f353d..f49f596 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,11 @@ Share printers from your Windows, Linux, FreeBSD or OS X computer with ChromeOS Lots of help can be found in [the wiki](https://github.com/google/cups-connector/wiki). +## Build Status +* Linux/OSX: [![Build Status](https://travis-ci.org/google/cups-connector.svg?branch=master)](https://travis-ci.org/google/cups-connector) + +* FreeBSD: [![Build Status](http://jenkins.mouf.net/job/cups-connector/badge/icon)](http://jenkins.mouf.net/job/cups-connector/) + ## License Copyright 2015 Google Inc. All rights reserved. From 433f5fb0506c2677cd697d41652fc0eb1b0a9147 Mon Sep 17 00:00:00 2001 From: Steven Rhodes Date: Tue, 22 Mar 2016 00:11:19 +0000 Subject: [PATCH 07/76] Parse page sizes specified in points, like "w81h252" See https://github.com/google/cups-connector/issues/203 --- cups/translate-ppd.go | 45 ++++++++++++++++++++++---------------- cups/translate-ppd_test.go | 2 ++ 2 files changed, 28 insertions(+), 19 deletions(-) diff --git a/cups/translate-ppd.go b/cups/translate-ppd.go index 417969a..d905122 100644 --- a/cups/translate-ppd.go +++ b/cups/translate-ppd.go @@ -88,6 +88,7 @@ var ( `(hpcups|hpijs|HPLIP),?\s+\d+(\.\d+)*|requires proprietary plugin` + `)\s*$`) rPageSize = regexp.MustCompile(`([\d.]+)(?:mm|in)?x([\d.]+)(mm|in)?`) + rPageSizeInPoints = regexp.MustCompile(`w([\d.]+)h([\d.]+)`) rColor = regexp.MustCompile(`(?i)^(?:cmy|rgb|color)`) rGray = regexp.MustCompile(`(?i)^(?:gray|black|mono)`) rCMAndResolutionPrefix = regexp.MustCompile(`(?i)^(?:on|off)\s*-?\s*`) @@ -391,7 +392,7 @@ func convertMargins(hwMargins string) *cdd.Margins { if intValue > 0 { marginsType = cdd.MarginsStandard } - marginsMicrons[i-1] = pointsToMicrons(intValue) + marginsMicrons[i-1] = pointsToMicrons(float32(intValue)) } // HWResolution format: left, bottom, right, top. @@ -718,6 +719,13 @@ func getCustomMediaSizeOption(optionKeyword, translation string) *cdd.MediaSizeO found = rPageSize.FindStringSubmatch(translation) } if found == nil { + found = rPageSizeInPoints.FindStringSubmatch(optionKeyword) + if found == nil { + return nil + } + found = append(found, "points") + } + if len(found) != 4 { return nil } @@ -730,22 +738,21 @@ func getCustomMediaSizeOption(optionKeyword, translation string) *cdd.MediaSizeO return nil } - if found[3] == "mm" { - return &cdd.MediaSizeOption{ - Name: cdd.MediaSizeCustom, - WidthMicrons: mmToMicrons(float32(width)), - HeightMicrons: mmToMicrons(float32(height)), - VendorID: optionKeyword, - CustomDisplayNameLocalized: cdd.NewLocalizedString(translation), - } - } else { - return &cdd.MediaSizeOption{ - Name: cdd.MediaSizeCustom, - WidthMicrons: inchesToMicrons(float32(width)), - HeightMicrons: inchesToMicrons(float32(height)), - VendorID: optionKeyword, - CustomDisplayNameLocalized: cdd.NewLocalizedString(translation), - } + var toMicrons func(float32) int32 + switch found[3] { + case "mm": + toMicrons = mmToMicrons + case "points": + toMicrons = pointsToMicrons + default: + toMicrons = inchesToMicrons + } + return &cdd.MediaSizeOption{ + Name: cdd.MediaSizeCustom, + WidthMicrons: toMicrons(float32(width)), + HeightMicrons: toMicrons(float32(height)), + VendorID: optionKeyword, + CustomDisplayNameLocalized: cdd.NewLocalizedString(translation), } } @@ -757,8 +764,8 @@ func mmToMicrons(mm float32) int32 { return int32(mm*1000 + 0.5) } -func pointsToMicrons(points int64) int32 { - return int32(float32(points*25400)/72 + 0.5) +func pointsToMicrons(points float32) int32 { + return int32(points*25400/72 + 0.5) } var ppdMediaSizes = map[string]cdd.MediaSizeOption{ diff --git a/cups/translate-ppd_test.go b/cups/translate-ppd_test.go index c5ccbe7..738eda5 100644 --- a/cups/translate-ppd_test.go +++ b/cups/translate-ppd_test.go @@ -50,6 +50,7 @@ func TestTrMediaSize(t *testing.T) { *PageSize B5/B5 - JIS: "" *PageSize Letter/Letter: "" *PageSize HalfLetter/5.5x8.5: "" +*PageSize w81h252/Address - 1 1/8 x 3 1/2": "<>setpagedevice" *CloseUI: *PageSize` expected := &cdd.PrinterDescriptionSection{ MediaSize: &cdd.MediaSize{ @@ -59,6 +60,7 @@ func TestTrMediaSize(t *testing.T) { cdd.MediaSizeOption{cdd.MediaSizeJISB5, mmToMicrons(182), mmToMicrons(257), false, false, "", "B5", cdd.NewLocalizedString("B5 (JIS)")}, cdd.MediaSizeOption{cdd.MediaSizeNALetter, inchesToMicrons(8.5), inchesToMicrons(11), false, true, "", "Letter", cdd.NewLocalizedString("Letter")}, cdd.MediaSizeOption{cdd.MediaSizeCustom, inchesToMicrons(5.5), inchesToMicrons(8.5), false, false, "", "HalfLetter", cdd.NewLocalizedString("5.5x8.5")}, + cdd.MediaSizeOption{cdd.MediaSizeCustom, pointsToMicrons(81), pointsToMicrons(252), false, false, "", "w81h252", cdd.NewLocalizedString(`Address - 1 1/8 x 3 1/2"`)}, }, }, } From aee65e86d4b6466bef1c31d56345f5986fb03c2d Mon Sep 17 00:00:00 2001 From: Jacob Marble Date: Mon, 29 Feb 2016 21:15:13 -0800 Subject: [PATCH 08/76] privet: HTTP API server port range. Instead of ephemeral ports, use ports from a selected range (default: 26000 to 26999). This makes packaging easier when the target system has to configure a firewall. Fixes #161 --- gcp-connector-util/init.go | 10 +++ gcp-connector-util/main_unix.go | 6 ++ gcp-connector-util/main_windows.go | 6 ++ gcp-cups-connector/gcp-cups-connector.go | 4 +- lib/config.go | 14 ++++ lib/config_unix.go | 9 +++ lib/config_windows.go | 9 +++ privet/api-server.go | 49 +------------ privet/portmanager.go | 90 ++++++++++++++++++++++++ privet/privet.go | 11 ++- 10 files changed, 157 insertions(+), 51 deletions(-) create mode 100644 privet/portmanager.go diff --git a/gcp-connector-util/init.go b/gcp-connector-util/init.go index c91ffbc..c628581 100644 --- a/gcp-connector-util/init.go +++ b/gcp-connector-util/init.go @@ -103,6 +103,16 @@ var commonInitFlags = []cli.Flag{ Usage: "Minimum event severity to log: FATAL, ERROR, WARNING, INFO, DEBUG", Value: lib.DefaultConfig.LogLevel, }, + cli.IntFlag{ + Name: "local-port-low", + Usage: "Local HTTP API server port range, low", + Value: int(lib.DefaultConfig.LocalPortLow), + }, + cli.IntFlag{ + Name: "local-port-high", + Usage: "Local HTTP API server port range, high", + Value: int(lib.DefaultConfig.LocalPortHigh), + }, } // getUserClientFromUser follows the token acquisition steps outlined here: diff --git a/gcp-connector-util/main_unix.go b/gcp-connector-util/main_unix.go index be24091..2c08cb6 100644 --- a/gcp-connector-util/main_unix.go +++ b/gcp-connector-util/main_unix.go @@ -138,6 +138,9 @@ func createCloudConfig(context *cli.Context, xmppJID, robotRefreshToken, userRef PrinterBlacklist: lib.DefaultConfig.PrinterBlacklist, LogLevel: context.String("log-level"), + LocalPortLow: uint16(context.Int("local-port-low")), + LocalPortHigh: uint16(context.Int("local-port-high")), + LogFileName: context.String("log-file-name"), LogFileMaxMegabytes: uint(context.Int("log-file-max-megabytes")), LogMaxFiles: uint(context.Int("log-max-files")), @@ -166,6 +169,9 @@ func createLocalConfig(context *cli.Context) *lib.Config { PrinterBlacklist: lib.DefaultConfig.PrinterBlacklist, LogLevel: context.String("log-level"), + LocalPortLow: uint16(context.Int("local-port-low")), + LocalPortHigh: uint16(context.Int("local-port-high")), + LogFileName: context.String("log-file-name"), LogFileMaxMegabytes: uint(context.Int("log-file-max-megabytes")), LogMaxFiles: uint(context.Int("log-max-files")), diff --git a/gcp-connector-util/main_windows.go b/gcp-connector-util/main_windows.go index fde19ac..919b1d1 100644 --- a/gcp-connector-util/main_windows.go +++ b/gcp-connector-util/main_windows.go @@ -225,6 +225,9 @@ func createCloudConfig(context *cli.Context, xmppJID, robotRefreshToken, userRef DisplayNamePrefix: context.String("display-name-prefix"), PrinterBlacklist: lib.DefaultConfig.PrinterBlacklist, LogLevel: context.String("log-level"), + + LocalPortLow: uint16(context.Int("local-port-low")), + LocalPortHigh: uint16(context.Int("local-port-high")), } } @@ -240,5 +243,8 @@ func createLocalConfig(context *cli.Context) *lib.Config { DisplayNamePrefix: context.String("display-name-prefix"), PrinterBlacklist: lib.DefaultConfig.PrinterBlacklist, LogLevel: context.String("log-level"), + + LocalPortLow: uint16(context.Int("local-port-low")), + LocalPortHigh: uint16(context.Int("local-port-high")), } } diff --git a/gcp-cups-connector/gcp-cups-connector.go b/gcp-cups-connector/gcp-cups-connector.go index c8ae4df..c40b382 100644 --- a/gcp-cups-connector/gcp-cups-connector.go +++ b/gcp-cups-connector/gcp-cups-connector.go @@ -167,9 +167,9 @@ func connector(context *cli.Context) int { var priv *privet.Privet if config.LocalPrintingEnable { if g == nil { - priv, err = privet.NewPrivet(jobs, config.GCPBaseURL, nil) + priv, err = privet.NewPrivet(jobs, config.LocalPortLow, config.LocalPortHigh, config.GCPBaseURL, nil) } else { - priv, err = privet.NewPrivet(jobs, config.GCPBaseURL, g.ProximityToken) + priv, err = privet.NewPrivet(jobs, config.LocalPortLow, config.LocalPortHigh, config.GCPBaseURL, g.ProximityToken) } if err != nil { log.Fatal(err) diff --git a/lib/config.go b/lib/config.go index 4d989e6..04fb36f 100644 --- a/lib/config.go +++ b/lib/config.go @@ -144,6 +144,14 @@ func (c *Config) commonSparse(context *cli.Context) *Config { s.DisplayNamePrefix == DefaultConfig.DisplayNamePrefix { s.DisplayNamePrefix = "" } + if !context.IsSet("local-port-low") && + s.LocalPortLow == DefaultConfig.LocalPortLow { + s.LocalPortLow = 0 + } + if !context.IsSet("local-port-high") && + s.LocalPortHigh == DefaultConfig.LocalPortHigh { + s.LocalPortHigh = 0 + } return &s } @@ -205,6 +213,12 @@ func (c *Config) commonBackfill(configMap map[string]interface{}) *Config { if _, exists := configMap["log_level"]; !exists { b.LogLevel = DefaultConfig.LogLevel } + if _, exists := configMap["local_port_low"]; !exists { + b.LocalPortLow = DefaultConfig.LocalPortLow + } + if _, exists := configMap["local_port_high"]; !exists { + b.LocalPortHigh = DefaultConfig.LocalPortHigh + } return &b } diff --git a/lib/config_unix.go b/lib/config_unix.go index 50271bc..91ab924 100644 --- a/lib/config_unix.go +++ b/lib/config_unix.go @@ -98,6 +98,12 @@ type Config struct { // Least severity to log. LogLevel string `json:"log_level"` + // Local only: HTTP API port range, low. + LocalPortLow uint16 `json:"local_port_low,omitempty"` + + // Local only: HTTP API port range, high. + LocalPortHigh uint16 `json:"local_port_high,omitempty"` + // CUPS only: Where to place log file. LogFileName string `json:"log_file_name"` @@ -161,6 +167,9 @@ var DefaultConfig = Config{ PrinterBlacklist: []string{}, LogLevel: "INFO", + LocalPortLow: 26000, + LocalPortHigh: 26999, + LogFileName: "/tmp/cups-connector", LogFileMaxMegabytes: 1, LogMaxFiles: 3, diff --git a/lib/config_windows.go b/lib/config_windows.go index badc1f6..3e2e139 100644 --- a/lib/config_windows.go +++ b/lib/config_windows.go @@ -95,6 +95,12 @@ type Config struct { // Least severity to log. LogLevel string `json:"log_level"` + + // Local only: HTTP API port range, low. + LocalPortLow uint16 `json:"local_port_low,omitempty"` + + // Local only: HTTP API port range, high. + LocalPortHigh uint16 `json:"local_port_high,omitempty"` } // DefaultConfig represents reasonable default values for Config fields. @@ -125,6 +131,9 @@ var DefaultConfig = Config{ LocalPrintingEnable: true, CloudPrintingEnable: false, LogLevel: "INFO", + + LocalPortLow: 26000, + LocalPortHigh: 26999, } // getConfigFilename gets the absolute filename of the config file specified by diff --git a/privet/api-server.go b/privet/api-server.go index ec6ff55..d86bee1 100644 --- a/privet/api-server.go +++ b/privet/api-server.go @@ -11,7 +11,6 @@ package privet import ( "encoding/json" "errors" - "fmt" "io" "io/ioutil" "net" @@ -85,11 +84,7 @@ type privetAPI struct { startTime time.Time } -func newPrivetAPI(gcpID, name, gcpBaseURL string, xsrf xsrfSecret, online bool, jc *jobCache, jobs chan<- *lib.Job, getPrinter func(string) (lib.Printer, bool), getProximityToken func(string, string) ([]byte, int, error)) (*privetAPI, error) { - l, err := newQuittableListener() - if err != nil { - return nil, err - } +func newPrivetAPI(gcpID, name, gcpBaseURL string, xsrf xsrfSecret, online bool, jc *jobCache, jobs chan<- *lib.Job, getPrinter func(string) (lib.Printer, bool), getProximityToken func(string, string) ([]byte, int, error), listener *quittableListener) (*privetAPI, error) { api := &privetAPI{ gcpID: gcpID, name: name, @@ -102,7 +97,7 @@ func newPrivetAPI(gcpID, name, gcpBaseURL string, xsrf xsrfSecret, online bool, getPrinter: getPrinter, getProximityToken: getProximityToken, - listener: l, + listener: listener, startTime: time.Now(), } go api.serve() @@ -494,43 +489,3 @@ func (api *privetAPI) jobstate(w http.ResponseWriter, r *http.Request) { w.Write(jobState) } - -type quittableListener struct { - *net.TCPListener - // When q is closed, the listener is quitting. - q chan struct{} -} - -func newQuittableListener() (*quittableListener, error) { - l, err := net.ListenTCP("tcp", nil) - if err != nil { - return nil, fmt.Errorf("Failed to start Privet API listener: %s", err) - } - return &quittableListener{l, make(chan struct{}, 0)}, nil -} - -func (l *quittableListener) Accept() (net.Conn, error) { - conn, err := l.AcceptTCP() - - select { - case <-l.q: - if err == nil { - conn.Close() - } - // The listener was closed on purpose. - // Returning an error that is not a net.Error causes net.Server.Serve() to return. - return nil, closed - default: - } - - // Clean up zombie connections. - conn.SetKeepAlive(true) - conn.SetKeepAlivePeriod(time.Minute) - - return conn, err -} - -func (l *quittableListener) quit() { - close(l.q) - l.Close() -} diff --git a/privet/portmanager.go b/privet/portmanager.go new file mode 100644 index 0000000..9f450b8 --- /dev/null +++ b/privet/portmanager.go @@ -0,0 +1,90 @@ +/* +Copyright 2016 Google Inc. All rights reserved. + +Use of this source code is governed by a BSD-style +license that can be found in the LICENSE file or at +https://developers.google.com/open-source/licenses/bsd +*/ + +package privet + +import ( + "errors" + "net" + "os" + "syscall" + "time" +) + +// portManager opens ports within the interval [min, max], starting with min. +type portManager struct { + min uint16 + max uint16 +} + +var NoPortsAvailable = errors.New("No ports available") + +// listen finds an open port, returns an open listener on that port. +// +// Returns error when no ports are available. +func (p *portManager) listen() (*quittableListener, error) { + for port := p.min; port <= p.max; port++ { + if l, err := newQuittableListener(port); err == nil { + return l, nil + } else { + if !isAddrInUse(err) { + return nil, err + } + } + } + return nil, NoPortsAvailable +} + +func isAddrInUse(err error) bool { + if err, ok := err.(*net.OpError); ok { + if err, ok := err.Err.(*os.SyscallError); ok { + return err.Err == syscall.EADDRINUSE + } + } + return false +} + +type quittableListener struct { + *net.TCPListener + // When q is closed, the listener is quitting. + q chan struct{} +} + +func newQuittableListener(port uint16) (*quittableListener, error) { + l, err := net.ListenTCP("tcp", &net.TCPAddr{Port: int(port)}) + if err != nil { + return nil, err + } + return &quittableListener{l, make(chan struct{}, 0)}, nil +} + +func (l *quittableListener) Accept() (net.Conn, error) { + conn, err := l.AcceptTCP() + + select { + case <-l.q: + if err == nil { + conn.Close() + } + // The listener was closed on purpose. + // Returning an error that is not a net.Error causes net.Server.Serve() to return. + return nil, closed + default: + } + + // Clean up zombie connections. + conn.SetKeepAlive(true) + conn.SetKeepAlivePeriod(time.Minute) + + return conn, err +} + +func (l *quittableListener) quit() { + close(l.q) + l.Close() +} diff --git a/privet/privet.go b/privet/privet.go index 46baee6..402e0c7 100644 --- a/privet/privet.go +++ b/privet/privet.go @@ -21,6 +21,7 @@ type Privet struct { apis map[string]*privetAPI apisMutex sync.RWMutex // Protects apis zc *zeroconf + pm *portManager jobs chan<- *lib.Job jc jobCache @@ -32,7 +33,7 @@ type Privet struct { // NewPrivet constructs a new Privet object. // // getProximityToken should be GoogleCloudPrint.ProximityToken() -func NewPrivet(jobs chan<- *lib.Job, gcpBaseURL string, getProximityToken func(string, string) ([]byte, int, error)) (*Privet, error) { +func NewPrivet(jobs chan<- *lib.Job, portLow, portHigh uint16, gcpBaseURL string, getProximityToken func(string, string) ([]byte, int, error)) (*Privet, error) { zc, err := newZeroconf() if err != nil { return nil, err @@ -42,6 +43,7 @@ func NewPrivet(jobs chan<- *lib.Job, gcpBaseURL string, getProximityToken func(s xsrf: newXSRFSecret(), apis: make(map[string]*privetAPI), zc: zc, + pm: &portManager{portLow, portHigh}, jobs: jobs, jc: *newJobCache(), @@ -60,7 +62,12 @@ func (p *Privet) AddPrinter(printer lib.Printer, getPrinter func(string) (lib.Pr online = true } - api, err := newPrivetAPI(printer.GCPID, printer.Name, p.gcpBaseURL, p.xsrf, online, &p.jc, p.jobs, getPrinter, p.getProximityToken) + listener, err := p.pm.listen() + if err != nil { + return err + } + + api, err := newPrivetAPI(printer.GCPID, printer.Name, p.gcpBaseURL, p.xsrf, online, &p.jc, p.jobs, getPrinter, p.getProximityToken, listener) if err != nil { return err } From 7438aa0f1ba665f7a8663aa6aa47bac6c3980f32 Mon Sep 17 00:00:00 2001 From: Jacob Marble Date: Mon, 29 Feb 2016 23:22:30 -0800 Subject: [PATCH 09/76] Improve HTTP API server listen() function. Added benchmark and test. BenchmarkListen_range1000_available100 took over one minute before this change; now it takes less than 500 ms. --- privet/portmanager.go | 72 +++++++++++++++-- privet/portmanager_test.go | 157 +++++++++++++++++++++++++++++++++++++ privet/privet.go | 2 +- 3 files changed, 222 insertions(+), 9 deletions(-) create mode 100644 privet/portmanager_test.go diff --git a/privet/portmanager.go b/privet/portmanager.go index 9f450b8..f32d523 100644 --- a/privet/portmanager.go +++ b/privet/portmanager.go @@ -12,24 +12,37 @@ import ( "errors" "net" "os" + "sync" "syscall" "time" ) -// portManager opens ports within the interval [min, max], starting with min. +var NoPortsAvailable = errors.New("No ports available") + +// portManager opens ports within the interval [low, high], starting with low. type portManager struct { - min uint16 - max uint16 + low uint16 + high uint16 + + // Keeping a cache of used ports improves benchmark tests by over 100x. + m sync.Mutex + p map[uint16]struct{} } -var NoPortsAvailable = errors.New("No ports available") +func newPortManager(low, high uint16) *portManager { + return &portManager{ + low: low, + high: high, + p: make(map[uint16]struct{}), + } +} // listen finds an open port, returns an open listener on that port. // // Returns error when no ports are available. func (p *portManager) listen() (*quittableListener, error) { - for port := p.min; port <= p.max; port++ { - if l, err := newQuittableListener(port); err == nil { + for port := p.nextAvailablePort(p.low); port != 0; port = p.nextAvailablePort(port) { + if l, err := newQuittableListener(port, p); err == nil { return l, nil } else { if !isAddrInUse(err) { @@ -37,9 +50,36 @@ func (p *portManager) listen() (*quittableListener, error) { } } } + return nil, NoPortsAvailable } +// nextAvailablePort checks the p map for the next port available. +// p only keeps track of ports used by the connector, so the start parameter +// is useful to check the port after a port that is in use by some other process. +// +// Returns zero when no available port can be found. +func (p *portManager) nextAvailablePort(start uint16) uint16 { + p.m.Lock() + defer p.m.Unlock() + + for port := start; port <= p.high; port++ { + if _, exists := p.p[port]; !exists { + p.p[port] = struct{}{} + return port + } + } + + return 0 +} + +func (p *portManager) freePort(port uint16) { + p.m.Lock() + defer p.m.Unlock() + + delete(p.p, port) +} + func isAddrInUse(err error) bool { if err, ok := err.(*net.OpError); ok { if err, ok := err.Err.(*os.SyscallError); ok { @@ -51,16 +91,19 @@ func isAddrInUse(err error) bool { type quittableListener struct { *net.TCPListener + + pm *portManager + // When q is closed, the listener is quitting. q chan struct{} } -func newQuittableListener(port uint16) (*quittableListener, error) { +func newQuittableListener(port uint16, pm *portManager) (*quittableListener, error) { l, err := net.ListenTCP("tcp", &net.TCPAddr{Port: int(port)}) if err != nil { return nil, err } - return &quittableListener{l, make(chan struct{}, 0)}, nil + return &quittableListener{l, pm, make(chan struct{}, 0)}, nil } func (l *quittableListener) Accept() (net.Conn, error) { @@ -84,6 +127,19 @@ func (l *quittableListener) Accept() (net.Conn, error) { return conn, err } +func (l *quittableListener) Close() error { + err := l.TCPListener.Close() + if err != nil { + return err + } + l.pm.freePort(l.port()) + return nil +} + +func (l *quittableListener) port() uint16 { + return uint16(l.Addr().(*net.TCPAddr).Port) +} + func (l *quittableListener) quit() { close(l.q) l.Close() diff --git a/privet/portmanager_test.go b/privet/portmanager_test.go new file mode 100644 index 0000000..17dd14b --- /dev/null +++ b/privet/portmanager_test.go @@ -0,0 +1,157 @@ +/* +Copyright 2016 Google Inc. All rights reserved. + +Use of this source code is governed by a BSD-style +license that can be found in the LICENSE file or at +https://developers.google.com/open-source/licenses/bsd +*/ + +package privet + +import "testing" + +const portLow = 26000 + +func TestListen_available1(t *testing.T) { + pm := newPortManager(portLow, portLow) + + l1, err := pm.listen() + if err != nil { + t.Fatal(err) + } + if l1.port() != portLow { + t.Logf("Expected port %d, got port %d", portLow, l1.port()) + l1.Close() + t.FailNow() + } + + l2, err := pm.listen() + if err == nil { + l1.Close() + l2.Close() + t.Fatal("Expected error when too many ports opened") + } + + err = l1.Close() + if err != nil { + t.Fatal(err) + } + + l3, err := pm.listen() + if err != nil { + t.Fatal(err) + } + if l3.port() != portLow { + t.Logf("Expected port %d, got port %d", portLow, l3.port()) + } + l3.Close() +} + +func TestListen_available2(t *testing.T) { + pm := newPortManager(portLow, portLow+1) + + l1, err := pm.listen() + if err != nil { + t.Fatal(err) + } + if l1.port() != portLow { + t.Logf("Expected port %d, got port %d", portLow, l1.port()) + l1.Close() + t.FailNow() + } + + l2, err := pm.listen() + if err != nil { + t.Fatal(err) + } + if l2.port() != portLow+1 { + t.Logf("Expected port %d, got port %d", portLow+1, l2.port()) + l2.Close() + t.FailNow() + } + + l3, err := pm.listen() + if err == nil { + l1.Close() + l2.Close() + l3.Close() + t.Fatal("Expected error when too many ports opened") + } + + err = l2.Close() + if err != nil { + l1.Close() + t.Fatal(err) + } + + l4, err := pm.listen() + if err != nil { + t.Fatal(err) + } + if l4.port() != portLow+1 { + t.Logf("Expected port %d, got port %d", portLow+1, l4.port()) + } + + l5, err := pm.listen() + if err == nil { + l1.Close() + l4.Close() + l5.Close() + t.Fatal("Expected error when too many ports opened") + } + + err = l1.Close() + if err != nil { + l4.Close() + t.Fatal(err) + } + + l6, err := pm.listen() + if err != nil { + t.Fatal(err) + } + if l6.port() != portLow { + t.Logf("Expected port %d, got port %d", portLow, l6.port()) + } + l4.Close() + l6.Close() +} + +// openPorts attempts to open n ports, where m are available. +func openPorts(n, m uint16) { + pm := newPortManager(portLow, portLow+m-1) + for i := uint16(0); i < n; i++ { + l, err := pm.listen() + if err == nil { + defer l.Close() + } + } +} + +func BenchmarkListen_range1_available1(*testing.B) { + openPorts(1, 1) +} + +func BenchmarkListen_range10_available10(*testing.B) { + openPorts(10, 10) +} + +func BenchmarkListen_range100_available100(*testing.B) { + openPorts(100, 100) +} + +func BenchmarkListen_range1000_available1000(*testing.B) { + openPorts(1000, 1000) +} + +func BenchmarkListen_range10_available1(*testing.B) { + openPorts(10, 1) +} + +func BenchmarkListen_range100_available10(*testing.B) { + openPorts(100, 10) +} + +func BenchmarkListen_range1000_available100(*testing.B) { + openPorts(1000, 100) +} diff --git a/privet/privet.go b/privet/privet.go index 402e0c7..752b6d8 100644 --- a/privet/privet.go +++ b/privet/privet.go @@ -43,7 +43,7 @@ func NewPrivet(jobs chan<- *lib.Job, portLow, portHigh uint16, gcpBaseURL string xsrf: newXSRFSecret(), apis: make(map[string]*privetAPI), zc: zc, - pm: &portManager{portLow, portHigh}, + pm: newPortManager(portLow, portHigh), jobs: jobs, jc: *newJobCache(), From 0d8d28a0b8afa3c53ae384c463f39fa6b554afcd Mon Sep 17 00:00:00 2001 From: Nick Westgate Date: Tue, 19 Apr 2016 04:52:23 +1200 Subject: [PATCH 10/76] Implemented cups_job_full_username option in Windows port (and more) (#217) * Implemented cups_job_full_username option in Windows port - Implemented cups_job_full_username option in Windows port - Changed the job status logic to treat paused jobs as "done" (because other software may pause jobs, and we aren't checking for the native printed/deleted statuses) - Renamed SetJob to SetJobCommand since it only uses commands - Added SetJobInfo1 & SetJobUserName which are used to change the username - Corrected error handling for: EnumPrinters, OpenPrinter, ClosePrinter, GetJob, SetJobCommand, StartDoc, EndDoc, StartPage, EndPage, SetGraphicsMode Note I have not added boilerplate "err == NO_ERROR" checks everywhere like SetWorldTransform has. Those "The operation completed successfully" Windows bugs are pretty rare. * Fixed Unix build error for CUPSJobFullUsername CUPSJobFullUsername had been moved above the CUPS only section. --- gcp-connector-util/main_windows.go | 2 + .../gcp-windows-connector.go | 2 +- lib/config.go | 7 ++ lib/config_unix.go | 7 +- lib/config_windows.go | 5 ++ winspool/win32.go | 68 +++++++++++++------ winspool/winspool.go | 12 ++-- 7 files changed, 72 insertions(+), 31 deletions(-) diff --git a/gcp-connector-util/main_windows.go b/gcp-connector-util/main_windows.go index 919b1d1..6c38542 100644 --- a/gcp-connector-util/main_windows.go +++ b/gcp-connector-util/main_windows.go @@ -221,6 +221,7 @@ func createCloudConfig(context *cli.Context, xmppJID, robotRefreshToken, userRef NativeJobQueueSize: uint(context.Int("native-job-queue-size")), NativePrinterPollInterval: context.String("native-printer-poll-interval"), + CUPSJobFullUsername: lib.PointerToBool(context.Bool("cups-job-full-username")), PrefixJobIDToJobTitle: lib.PointerToBool(context.Bool("prefix-job-id-to-job-title")), DisplayNamePrefix: context.String("display-name-prefix"), PrinterBlacklist: lib.DefaultConfig.PrinterBlacklist, @@ -239,6 +240,7 @@ func createLocalConfig(context *cli.Context) *lib.Config { NativeJobQueueSize: uint(context.Int("native-job-queue-size")), NativePrinterPollInterval: context.String("native-printer-poll-interval"), + CUPSJobFullUsername: lib.PointerToBool(context.Bool("cups-job-full-username")), PrefixJobIDToJobTitle: lib.PointerToBool(context.Bool("prefix-job-id-to-job-title")), DisplayNamePrefix: context.String("display-name-prefix"), PrinterBlacklist: lib.DefaultConfig.PrinterBlacklist, diff --git a/gcp-windows-connector/gcp-windows-connector.go b/gcp-windows-connector/gcp-windows-connector.go index 97528f1..d2dbc7a 100644 --- a/gcp-windows-connector/gcp-windows-connector.go +++ b/gcp-windows-connector/gcp-windows-connector.go @@ -161,7 +161,7 @@ func (service *service) Execute(args []string, r <-chan svc.ChangeRequest, s cha return false, 1 } pm, err := manager.NewPrinterManager(ws, g, nil, nativePrinterPollInterval, - config.NativeJobQueueSize, false, config.ShareScope, jobs, xmppNotifications) + config.NativeJobQueueSize, *config.CUPSJobFullUsername, config.ShareScope, jobs, xmppNotifications) if err != nil { log.Fatal(err) return false, 1 diff --git a/lib/config.go b/lib/config.go index 04fb36f..480c258 100644 --- a/lib/config.go +++ b/lib/config.go @@ -136,6 +136,10 @@ func (c *Config) commonSparse(context *cli.Context) *Config { s.NativePrinterPollInterval == DefaultConfig.NativePrinterPollInterval { s.NativePrinterPollInterval = "" } + if !context.IsSet("cups-job-full-username") && + reflect.DeepEqual(s.CUPSJobFullUsername, DefaultConfig.CUPSJobFullUsername) { + s.CUPSJobFullUsername = nil + } if !context.IsSet("prefix-job-id-to-job-title") && reflect.DeepEqual(s.PrefixJobIDToJobTitle, DefaultConfig.PrefixJobIDToJobTitle) { s.PrefixJobIDToJobTitle = nil @@ -195,6 +199,9 @@ func (c *Config) commonBackfill(configMap map[string]interface{}) *Config { if _, exists := configMap["cups_printer_poll_interval"]; !exists { b.NativePrinterPollInterval = DefaultConfig.NativePrinterPollInterval } + if _, exists := configMap["cups_job_full_username"]; !exists { + b.CUPSJobFullUsername = DefaultConfig.CUPSJobFullUsername + } if _, exists := configMap["prefix_job_id_to_job_title"]; !exists { b.PrefixJobIDToJobTitle = DefaultConfig.PrefixJobIDToJobTitle } diff --git a/lib/config_unix.go b/lib/config_unix.go index 91ab924..2acca50 100644 --- a/lib/config_unix.go +++ b/lib/config_unix.go @@ -86,6 +86,10 @@ type Config struct { // TODO: rename without cups_ prefix NativePrinterPollInterval string `json:"cups_printer_poll_interval,omitempty"` + // Use the full username (joe@example.com) in job. + // TODO: rename without cups_ prefix + CUPSJobFullUsername *bool `json:"cups_job_full_username,omitempty"` + // Add the job ID to the beginning of the job title. Useful for debugging. PrefixJobIDToJobTitle *bool `json:"prefix_job_id_to_job_title,omitempty"` @@ -128,9 +132,6 @@ type Config struct { // CUPS only: printer attributes to copy to GCP. CUPSPrinterAttributes []string `json:"cups_printer_attributes,omitempty"` - // CUPS only: use the full username (joe@example.com) in CUPS job. - CUPSJobFullUsername *bool `json:"cups_job_full_username,omitempty"` - // CUPS only: ignore printers with make/model 'Local Raw Printer'. CUPSIgnoreRawPrinters *bool `json:"cups_ignore_raw_printers,omitempty"` diff --git a/lib/config_windows.go b/lib/config_windows.go index 3e2e139..ffbaa02 100644 --- a/lib/config_windows.go +++ b/lib/config_windows.go @@ -84,6 +84,10 @@ type Config struct { // TODO: rename without cups_ prefix NativePrinterPollInterval string `json:"cups_printer_poll_interval,omitempty"` + // Use the full username (joe@example.com) in job. + // TODO: rename without cups_ prefix + CUPSJobFullUsername *bool `json:"cups_job_full_username,omitempty"` + // Add the job ID to the beginning of the job title. Useful for debugging. PrefixJobIDToJobTitle *bool `json:"prefix_job_id_to_job_title,omitempty"` @@ -120,6 +124,7 @@ var DefaultConfig = Config{ NativeJobQueueSize: 3, NativePrinterPollInterval: "1m", + CUPSJobFullUsername: PointerToBool(false), PrefixJobIDToJobTitle: PointerToBool(false), DisplayNamePrefix: "", PrinterBlacklist: []string{ diff --git a/winspool/win32.go b/winspool/win32.go index 0669c4c..d97897e 100644 --- a/winspool/win32.go +++ b/winspool/win32.go @@ -573,8 +573,8 @@ func enumPrinters(level uint32) ([]byte, uint32, error) { } var pPrinterEnum []byte = make([]byte, cbBuf) - _, _, err = enumPrintersProc.Call(PRINTER_ENUM_LOCAL, 0, uintptr(level), uintptr(unsafe.Pointer(&pPrinterEnum[0])), uintptr(cbBuf), uintptr(unsafe.Pointer(&cbBuf)), uintptr(unsafe.Pointer(&pcReturned))) - if err != NO_ERROR { + r1, _, err := enumPrintersProc.Call(PRINTER_ENUM_LOCAL, 0, uintptr(level), uintptr(unsafe.Pointer(&pPrinterEnum[0])), uintptr(cbBuf), uintptr(unsafe.Pointer(&cbBuf)), uintptr(unsafe.Pointer(&pcReturned))) + if r1 == 0 { return nil, 0, err } @@ -606,16 +606,16 @@ func OpenPrinter(printerName string) (HANDLE, error) { } var hPrinter HANDLE - _, _, err = openPrinterProc.Call(uintptr(unsafe.Pointer(pPrinterName)), uintptr(unsafe.Pointer(&hPrinter)), 0) - if err != NO_ERROR { + r1, _, err := openPrinterProc.Call(uintptr(unsafe.Pointer(pPrinterName)), uintptr(unsafe.Pointer(&hPrinter)), 0) + if r1 == 0 { return 0, err } return hPrinter, nil } func (hPrinter *HANDLE) ClosePrinter() error { - _, _, err := closePrinterProc.Call(uintptr(*hPrinter)) - if err != NO_ERROR { + r1, _, err := closePrinterProc.Call(uintptr(*hPrinter)) + if r1 == 0 { return err } *hPrinter = 0 @@ -726,8 +726,8 @@ func (hPrinter HANDLE) GetJob(jobID int32) (*JobInfo1, error) { } var pJob []byte = make([]byte, cbBuf) - _, _, err = getJobProc.Call(uintptr(hPrinter), uintptr(jobID), 1, uintptr(unsafe.Pointer(&pJob[0])), uintptr(cbBuf), uintptr(unsafe.Pointer(&cbBuf))) - if err != NO_ERROR { + r1, _, err := getJobProc.Call(uintptr(hPrinter), uintptr(jobID), 1, uintptr(unsafe.Pointer(&pJob[0])), uintptr(cbBuf), uintptr(unsafe.Pointer(&cbBuf))) + if r1 == 0 { return nil, err } @@ -749,12 +749,39 @@ const ( JOB_CONTROL_RELEASE uint32 = 9 ) -func (hPrinter HANDLE) SetJob(jobID int32, command uint32) error { - _, _, err := setJobProc.Call(uintptr(hPrinter), uintptr(jobID), 0, 0, uintptr(command)) - if err != NO_ERROR { +func (hPrinter HANDLE) SetJobCommand(jobID int32, command uint32) error { + r1, _, err := setJobProc.Call(uintptr(hPrinter), uintptr(jobID), 0, 0, uintptr(command)) + if r1 == 0 { + return err + } + return nil +} + +func (hPrinter HANDLE) SetJobInfo1(jobID int32, ji1 *JobInfo1) error { + r1, _, err := setJobProc.Call(uintptr(hPrinter), uintptr(jobID), 1, uintptr(unsafe.Pointer(ji1)), 0) + if r1 == 0 { + return err + } + return nil +} + +func (hPrinter HANDLE) SetJobUserName(jobID int32, userName string) error { + ji1, err := hPrinter.GetJob(jobID) + if err != nil { return err } + pUserName, err := syscall.UTF16PtrFromString(userName) + if err != nil { + return err + } + + ji1.pUserName = pUserName; + ji1.position = 0; // To prevent a possible access denied error (0 is JOB_POSITION_UNSPECIFIED) + err = hPrinter.SetJobInfo1(jobID, ji1) + if err != nil { + return err + } return nil } @@ -769,7 +796,6 @@ func CreateDC(deviceName string, devMode *DevMode) (HDC, error) { if r1 == 0 { return 0, err } - return HDC(r1), nil } @@ -806,15 +832,15 @@ func (hDC HDC) StartDoc(docName string) (int32, error) { } r1, _, err := startDocProc.Call(uintptr(hDC), uintptr(unsafe.Pointer(&docInfo))) - if err != NO_ERROR { + if r1 <= 0 { return 0, err } return int32(r1), nil } func (hDC HDC) EndDoc() error { - _, _, err := endDocProc.Call(uintptr(hDC)) - if err != NO_ERROR { + r1, _, err := endDocProc.Call(uintptr(hDC)) + if r1 <= 0 { return err } return nil @@ -827,16 +853,16 @@ func (hDC HDC) AbortDoc() error { } func (hDC HDC) StartPage() error { - _, _, err := startPageProc.Call(uintptr(hDC)) - if err != NO_ERROR { + r1, _, err := startPageProc.Call(uintptr(hDC)) + if r1 <= 0 { return err } return nil } func (hDC HDC) EndPage() error { - _, _, err := endPageProc.Call(uintptr(hDC)) - if err != NO_ERROR { + r1, _, err := endPageProc.Call(uintptr(hDC)) + if r1 <= 0 { return err } return nil @@ -848,8 +874,8 @@ const ( ) func (hDC HDC) SetGraphicsMode(iMode int32) error { - _, _, err := setGraphicsModeProc.Call(uintptr(hDC), uintptr(iMode)) - if err != NO_ERROR { + r1, _, err := setGraphicsModeProc.Call(uintptr(hDC), uintptr(iMode)) + if r1 == 0 { return err } return nil diff --git a/winspool/winspool.go b/winspool/winspool.go index f28eeb9..907620b 100644 --- a/winspool/winspool.go +++ b/winspool/winspool.go @@ -545,8 +545,7 @@ func convertJobState(wsStatus uint32) *cdd.JobState { state.Type = cdd.JobStateDone } else if wsStatus&JOB_STATUS_PAUSED != 0 || wsStatus == 0 { - state.Type = cdd.JobStateStopped - state.UserActionCause = &cdd.UserActionCause{cdd.UserActionCausePaused} + state.Type = cdd.JobStateDone } else if wsStatus&JOB_STATUS_ERROR != 0 { state.Type = cdd.JobStateAborted @@ -606,7 +605,7 @@ type jobContext struct { cContext CairoContext } -func newJobContext(printerName, fileName, title string) (*jobContext, error) { +func newJobContext(printerName, fileName, title, user string) (*jobContext, error) { pDoc, err := PopplerDocumentNewFromFile(fileName) if err != nil { return nil, err @@ -641,7 +640,7 @@ func newJobContext(printerName, fileName, title string) (*jobContext, error) { pDoc.Unref() return nil, err } - err = hPrinter.SetJob(jobID, JOB_CONTROL_RETAIN) + err = hPrinter.SetJobCommand(jobID, JOB_CONTROL_RETAIN) if err != nil { hDC.EndDoc() hDC.DeleteDC() @@ -649,6 +648,7 @@ func newJobContext(printerName, fileName, title string) (*jobContext, error) { pDoc.Unref() return nil, err } + hPrinter.SetJobUserName(jobID, user) cSurface, err := CairoWin32PrintingSurfaceCreate(hDC) if err != nil { hDC.EndDoc() @@ -680,7 +680,7 @@ func (c *jobContext) free() error { if err != nil { return err } - err = c.hPrinter.SetJob(c.jobID, JOB_CONTROL_RELEASE) + err = c.hPrinter.SetJobCommand(c.jobID, JOB_CONTROL_RELEASE) if err != nil { return err } @@ -829,7 +829,7 @@ func (ws *WinSpool) Print(printer *lib.Printer, fileName, title, user, gcpJobID return 0, errors.New("Print() called with nil ticket") } - jobContext, err := newJobContext(printer.Name, fileName, title) + jobContext, err := newJobContext(printer.Name, fileName, title, user) if err != nil { return 0, err } From 62f24f5a5c1240010ac1176752e1a5aa17726629 Mon Sep 17 00:00:00 2001 From: Adam Goode Date: Mon, 6 Jun 2016 16:04:57 -0400 Subject: [PATCH 11/76] Add mailing list info to README.md (#240) --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index f49f596..71ee725 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,9 @@ Share printers from your Windows, Linux, FreeBSD or OS X computer with ChromeOS Lots of help can be found in [the wiki](https://github.com/google/cups-connector/wiki). +## Mailing list +Please join the mailing list at https://groups.google.com/forum/#!forum/cups-connector. Anyone can post and view messages. + ## Build Status * Linux/OSX: [![Build Status](https://travis-ci.org/google/cups-connector.svg?branch=master)](https://travis-ci.org/google/cups-connector) From 0c86a83da6bda1b3950ced09fd2dc695cd2544d4 Mon Sep 17 00:00:00 2001 From: Adam Goode Date: Mon, 6 Jun 2016 16:05:59 -0400 Subject: [PATCH 12/76] Correct the use of color when translated from attrs (not PPD) (#234) Colors were being set in the CDD and ticket structures as VendorKey:"", VendorId:"key:value". Now we properly do VendorKey:"key", VendorId:"value" so things decode properly later. --- cups/translate-attrs.go | 10 +++++----- cups/translate-attrs_test.go | 9 +++++---- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/cups/translate-attrs.go b/cups/translate-attrs.go index 8296c8c..317787f 100644 --- a/cups/translate-attrs.go +++ b/cups/translate-attrs.go @@ -434,17 +434,17 @@ func convertCopies(printerTags map[string][]string) *cdd.Copies { var colorByKeyword = map[string]cdd.ColorOption{ "auto": cdd.ColorOption{ - VendorID: fmt.Sprintf("%s%s%s", attrPrintColorMode, internalKeySeparator, "auto"), + VendorID: "auto", Type: cdd.ColorTypeAuto, CustomDisplayNameLocalized: cdd.NewLocalizedString("Auto"), }, "color": cdd.ColorOption{ - VendorID: fmt.Sprintf("%s%s%s", attrPrintColorMode, internalKeySeparator, "color"), + VendorID: "color", Type: cdd.ColorTypeStandardColor, CustomDisplayNameLocalized: cdd.NewLocalizedString("Color"), }, "monochrome": cdd.ColorOption{ - VendorID: fmt.Sprintf("%s%s%s", attrPrintColorMode, internalKeySeparator, "monochrome"), + VendorID: "monochrome", Type: cdd.ColorTypeStandardMonochrome, CustomDisplayNameLocalized: cdd.NewLocalizedString("Monochrome"), }, @@ -461,13 +461,13 @@ func convertColorAttrs(printerTags map[string][]string) *cdd.Color { colorDefault = colorSupported[:1] } - var c cdd.Color + c := cdd.Color{VendorKey: attrPrintColorMode} for _, color := range colorSupported { var co cdd.ColorOption var exists bool if co, exists = colorByKeyword[color]; !exists { co = cdd.ColorOption{ - VendorID: fmt.Sprintf("%s%s%s", attrPrintColorMode, internalKeySeparator, color), + VendorID: color, Type: cdd.ColorTypeCustomColor, CustomDisplayNameLocalized: cdd.NewLocalizedString(color), } diff --git a/cups/translate-attrs_test.go b/cups/translate-attrs_test.go index 34fa172..3549e8b 100644 --- a/cups/translate-attrs_test.go +++ b/cups/translate-attrs_test.go @@ -585,11 +585,12 @@ func TestConvertColorAttrs(t *testing.T) { } expected := &cdd.Color{ Option: []cdd.ColorOption{ - cdd.ColorOption{"print-color-mode:color", cdd.ColorTypeStandardColor, "", false, cdd.NewLocalizedString("Color")}, - cdd.ColorOption{"print-color-mode:monochrome", cdd.ColorTypeStandardMonochrome, "", false, cdd.NewLocalizedString("Monochrome")}, - cdd.ColorOption{"print-color-mode:auto", cdd.ColorTypeAuto, "", true, cdd.NewLocalizedString("Auto")}, - cdd.ColorOption{"print-color-mode:zebra", cdd.ColorTypeCustomColor, "", false, cdd.NewLocalizedString("zebra")}, + cdd.ColorOption{"color", cdd.ColorTypeStandardColor, "", false, cdd.NewLocalizedString("Color")}, + cdd.ColorOption{"monochrome", cdd.ColorTypeStandardMonochrome, "", false, cdd.NewLocalizedString("Monochrome")}, + cdd.ColorOption{"auto", cdd.ColorTypeAuto, "", true, cdd.NewLocalizedString("Auto")}, + cdd.ColorOption{"zebra", cdd.ColorTypeCustomColor, "", false, cdd.NewLocalizedString("zebra")}, }, + VendorKey: "print-color-mode", } c = convertColorAttrs(pt) if !reflect.DeepEqual(expected, c) { From e79d3e1ab5f676fc39f94334ea0f5ef43640f619 Mon Sep 17 00:00:00 2001 From: Adam Goode Date: Mon, 6 Jun 2016 16:06:40 -0400 Subject: [PATCH 13/76] Add omitempty to fields marked (optional) in the privet specification (#236) All the other json specifications seemed correct, only infoResponse was missing omitempty annotations. See https://developers.google.com/cloud-print/docs/privet#42-privetinfo-api --- privet/api-server.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/privet/api-server.go b/privet/api-server.go index d86bee1..5033a73 100644 --- a/privet/api-server.go +++ b/privet/api-server.go @@ -133,7 +133,7 @@ func (api *privetAPI) serve() { type infoResponse struct { Version string `json:"version"` Name string `json:"name"` - Description string `json:"description"` + Description string `json:"description,omitempty"` URL string `json:"url"` Type []string `json:"type"` ID string `json:"id"` @@ -141,15 +141,15 @@ type infoResponse struct { ConnectionState string `json:"connection_state"` Manufacturer string `json:"manufacturer"` Model string `json:"model"` - SerialNumber string `json:"serial_number"` + SerialNumber string `json:"serial_number,omitempty"` Firmware string `json:"firmware"` Uptime uint `json:"uptime"` - SetupURL string `json:"setup_url"` - SupportURL string `json:"support_url"` - UpdateURL string `json:"update_url"` + SetupURL string `json:"setup_url,omitempty"` + SupportURL string `json:"support_url,omitempty"` + UpdateURL string `json:"update_url,omitempty"` XPrivetToken string `json:"x-privet-token"` API []string `json:"api"` - SemanticState cdd.CloudDeviceState `json:"semantic_state"` + SemanticState cdd.CloudDeviceState `json:"semantic_state,omitempty"` } func (api *privetAPI) info(w http.ResponseWriter, r *http.Request) { From f0989d2cf3ba0de027ed5e82eeb821687722240f Mon Sep 17 00:00:00 2001 From: Adam Goode Date: Mon, 6 Jun 2016 16:17:16 -0400 Subject: [PATCH 14/76] Add support for the optional "note" privet TXT field that matches "description" in /privet/info (#237) --- privet/avahi.go | 21 +++++++++++++++------ privet/bonjour.c | 14 ++++++++++++-- privet/bonjour.go | 12 ++++++++---- privet/bonjour.h | 6 +++--- privet/privet.go | 4 ++-- 5 files changed, 40 insertions(+), 17 deletions(-) diff --git a/privet/avahi.go b/privet/avahi.go index cbb8dfa..64778b2 100644 --- a/privet/avahi.go +++ b/privet/avahi.go @@ -26,6 +26,7 @@ var ( txtversKey = C.CString("txtvers") txtversValue = C.CString("1") tyKey = C.CString("ty") + noteKey = C.CString("note") urlKey = C.CString("url") typeKey = C.CString("type") typeValue = C.CString("printer") @@ -41,6 +42,7 @@ type record struct { name *C.char port uint16 ty string + note string url string id string online bool @@ -81,7 +83,7 @@ func newZeroconf() (*zeroconf, error) { return &z, nil } -func prepareTXT(ty, url, id string, online bool) *C.AvahiStringList { +func prepareTXT(ty, note, url, id string, online bool) *C.AvahiStringList { var txt *C.AvahiStringList txt = C.avahi_string_list_add_pair(txt, txtversKey, txtversValue) txt = C.avahi_string_list_add_pair(txt, typeKey, typeValue) @@ -90,6 +92,12 @@ func prepareTXT(ty, url, id string, online bool) *C.AvahiStringList { defer C.free(unsafe.Pointer(tyValue)) txt = C.avahi_string_list_add_pair(txt, tyKey, tyValue) + if note != "" { + noteValue := C.CString(note) + defer C.free(unsafe.Pointer(noteValue)) + txt = C.avahi_string_list_add_pair(txt, noteKey, noteValue) + } + urlValue := C.CString(url) defer C.free(unsafe.Pointer(urlValue)) txt = C.avahi_string_list_add_pair(txt, urlKey, urlValue) @@ -107,11 +115,12 @@ func prepareTXT(ty, url, id string, online bool) *C.AvahiStringList { return txt } -func (z *zeroconf) addPrinter(name string, port uint16, ty, url, id string, online bool) error { +func (z *zeroconf) addPrinter(name string, port uint16, ty, note, url, id string, online bool) error { r := record{ name: C.CString(name), port: port, ty: ty, + note: note, url: url, id: id, online: online, @@ -124,7 +133,7 @@ func (z *zeroconf) addPrinter(name string, port uint16, ty, url, id string, onli return fmt.Errorf("printer %s was already added to Avahi publishing", name) } if z.state == C.AVAHI_CLIENT_S_RUNNING { - txt := prepareTXT(ty, url, id, online) + txt := prepareTXT(ty, note, url, id, online) defer C.avahi_string_list_free(txt) C.avahi_threaded_poll_lock(z.threadedPoll) @@ -140,7 +149,7 @@ func (z *zeroconf) addPrinter(name string, port uint16, ty, url, id string, onli return nil } -func (z *zeroconf) updatePrinterTXT(name, ty, url, id string, online bool) error { +func (z *zeroconf) updatePrinterTXT(name, ty, note, url, id string, online bool) error { z.spMutex.Lock() defer z.spMutex.Unlock() @@ -155,7 +164,7 @@ func (z *zeroconf) updatePrinterTXT(name, ty, url, id string, online bool) error r.online = online if z.state == C.AVAHI_CLIENT_S_RUNNING && r.group != nil { - txt := prepareTXT(ty, url, id, online) + txt := prepareTXT(ty, note, url, id, online) defer C.avahi_string_list_free(txt) C.avahi_threaded_poll_lock(z.threadedPoll) @@ -262,7 +271,7 @@ func handleClientStateChange(client *C.AvahiClient, newState C.AvahiClientState, if newState == C.AVAHI_CLIENT_S_RUNNING { log.Info("Local printing enabled (Avahi client is running).") for name, r := range z.printers { - txt := prepareTXT(r.ty, r.url, r.id, r.online) + txt := prepareTXT(r.ty, r.note, r.url, r.id, r.online) defer C.avahi_string_list_free(txt) if errstr := C.addAvahiGroup(z.threadedPoll, z.client, &r.group, r.name, C.ushort(r.port), txt); errstr != nil { diff --git a/privet/bonjour.c b/privet/bonjour.c index 39b0c06..65b4080 100644 --- a/privet/bonjour.c +++ b/privet/bonjour.c @@ -51,10 +51,11 @@ void registerCallback(CFNetServiceRef service, CFStreamError *streamError, void // startBonjour starts and returns a bonjour service. // // Returns a registered service. Returns NULL and sets err on failure. -CFNetServiceRef startBonjour(const char *name, const char *type, unsigned short int port, const char *ty, const char *url, const char *id, const char *cs, char **err) { +CFNetServiceRef startBonjour(const char *name, const char *type, unsigned short int port, const char *ty, const char *note, const char *url, const char *id, const char *cs, char **err) { CFStringRef nameCF = CFStringCreateWithCString(NULL, name, kCFStringEncodingASCII); CFStringRef typeCF = CFStringCreateWithCString(NULL, type, kCFStringEncodingASCII); CFStringRef tyCF = CFStringCreateWithCString(NULL, ty, kCFStringEncodingASCII); + CFStringRef noteCF = CFStringCreateWithCString(NULL, note, kCFStringEncodingASCII); CFStringRef urlCF = CFStringCreateWithCString(NULL, url, kCFStringEncodingASCII); CFStringRef idCF = CFStringCreateWithCString(NULL, id, kCFStringEncodingASCII); CFStringRef csCF = CFStringCreateWithCString(NULL, cs, kCFStringEncodingASCII); @@ -63,6 +64,9 @@ CFNetServiceRef startBonjour(const char *name, const char *type, unsigned short &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); CFDictionarySetValue(dict, CFSTR("txtvers"), CFSTR("1")); CFDictionarySetValue(dict, CFSTR("ty"), tyCF); + if (CFStringGetLength(noteCF) > 0) { + CFDictionarySetValue(dict, CFSTR("note"), noteCF); + } CFDictionarySetValue(dict, CFSTR("url"), urlCF); CFDictionarySetValue(dict, CFSTR("type"), CFSTR("printer")); CFDictionarySetValue(dict, CFSTR("id"), idCF); @@ -89,6 +93,7 @@ CFNetServiceRef startBonjour(const char *name, const char *type, unsigned short CFRelease(typeCF); CFRelease(tyCF); + CFRelease(noteCF); CFRelease(urlCF); CFRelease(idCF); CFRelease(csCF); @@ -99,8 +104,9 @@ CFNetServiceRef startBonjour(const char *name, const char *type, unsigned short } // updateBonjour updates the TXT record of service. -void updateBonjour(CFNetServiceRef service, const char *ty, const char *url, const char *id, const char *cs) { +void updateBonjour(CFNetServiceRef service, const char *ty, const char *note, const char *url, const char *id, const char *cs) { CFStringRef tyCF = CFStringCreateWithCString(NULL, ty, kCFStringEncodingASCII); + CFStringRef noteCF = CFStringCreateWithCString(NULL, note, kCFStringEncodingASCII); CFStringRef urlCF = CFStringCreateWithCString(NULL, url, kCFStringEncodingASCII); CFStringRef idCF = CFStringCreateWithCString(NULL, id, kCFStringEncodingASCII); CFStringRef csCF = CFStringCreateWithCString(NULL, cs, kCFStringEncodingASCII); @@ -109,6 +115,9 @@ void updateBonjour(CFNetServiceRef service, const char *ty, const char *url, con &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); CFDictionarySetValue(dict, CFSTR("txtvers"), CFSTR("1")); CFDictionarySetValue(dict, CFSTR("ty"), tyCF); + if (CFStringGetLength(noteCF) > 0) { + CFDictionarySetValue(dict, CFSTR("note"), noteCF); + } CFDictionarySetValue(dict, CFSTR("url"), urlCF); CFDictionarySetValue(dict, CFSTR("type"), CFSTR("printer")); CFDictionarySetValue(dict, CFSTR("id"), idCF); @@ -118,6 +127,7 @@ void updateBonjour(CFNetServiceRef service, const char *ty, const char *url, con CFNetServiceSetTXTData(service, txt); CFRelease(tyCF); + CFRelease(noteCF); CFRelease(urlCF); CFRelease(idCF); CFRelease(csCF); diff --git a/privet/bonjour.go b/privet/bonjour.go index bfc985e..1dc78f5 100644 --- a/privet/bonjour.go +++ b/privet/bonjour.go @@ -38,7 +38,7 @@ func newZeroconf() (*zeroconf, error) { return &z, nil } -func (z *zeroconf) addPrinter(name string, port uint16, ty, url, id string, online bool) error { +func (z *zeroconf) addPrinter(name string, port uint16, ty, note, url, id string, online bool) error { z.pMutex.RLock() if _, exists := z.printers[name]; exists { z.pMutex.RUnlock() @@ -52,6 +52,8 @@ func (z *zeroconf) addPrinter(name string, port uint16, ty, url, id string, onli defer C.free(unsafe.Pointer(serviceTypeC)) tyC := C.CString(ty) defer C.free(unsafe.Pointer(tyC)) + noteC := C.CString(note) + defer C.free(unsafe.Pointer(noteC)) urlC := C.CString(url) defer C.free(unsafe.Pointer(urlC)) idC := C.CString(id) @@ -65,7 +67,7 @@ func (z *zeroconf) addPrinter(name string, port uint16, ty, url, id string, onli defer C.free(unsafe.Pointer(onlineC)) var errstr *C.char = nil - service := C.startBonjour(nameC, serviceTypeC, C.ushort(port), tyC, urlC, idC, onlineC, &errstr) + service := C.startBonjour(nameC, serviceTypeC, C.ushort(port), tyC, noteC, urlC, idC, onlineC, &errstr) if errstr != nil { defer C.free(unsafe.Pointer(errstr)) return errors.New(C.GoString(errstr)) @@ -79,9 +81,11 @@ func (z *zeroconf) addPrinter(name string, port uint16, ty, url, id string, onli } // updatePrinterTXT updates the advertised TXT record. -func (z *zeroconf) updatePrinterTXT(name, ty, url, id string, online bool) error { +func (z *zeroconf) updatePrinterTXT(name, ty, note, url, id string, online bool) error { tyC := C.CString(ty) defer C.free(unsafe.Pointer(tyC)) + noteC := C.CString(note) + defer C.free(unsafe.Pointer(noteC)) urlC := C.CString(url) defer C.free(unsafe.Pointer(urlC)) idC := C.CString(id) @@ -98,7 +102,7 @@ func (z *zeroconf) updatePrinterTXT(name, ty, url, id string, online bool) error defer z.pMutex.RUnlock() if service, exists := z.printers[name]; exists { - C.updateBonjour(service, tyC, urlC, idC, onlineC) + C.updateBonjour(service, tyC, noteC, urlC, idC, onlineC) } else { return fmt.Errorf("Bonjour can't update printer %s that hasn't been added", name) } diff --git a/privet/bonjour.h b/privet/bonjour.h index ab4aaec..3e5a642 100644 --- a/privet/bonjour.h +++ b/privet/bonjour.h @@ -14,8 +14,8 @@ #include // free CFNetServiceRef startBonjour(const char *name, const char *type, - unsigned short int port, const char *ty, const char *url, const char *id, - const char *cs, char **err); -void updateBonjour(CFNetServiceRef service, const char *ty, const char *url, + unsigned short int port, const char *ty, const char *note, const char *url, + const char *id, const char *cs, char **err); +void updateBonjour(CFNetServiceRef service, const char *ty, const char *note, const char *url, const char *id, const char *cs); void stopBonjour(CFNetServiceRef service); diff --git a/privet/privet.go b/privet/privet.go index 752b6d8..d42b659 100644 --- a/privet/privet.go +++ b/privet/privet.go @@ -76,7 +76,7 @@ func (p *Privet) AddPrinter(printer lib.Printer, getPrinter func(string) (lib.Pr if online { localDefaultDisplayName = fmt.Sprintf("%s (local)", localDefaultDisplayName) } - err = p.zc.addPrinter(printer.Name, api.port(), localDefaultDisplayName, p.gcpBaseURL, printer.GCPID, online) + err = p.zc.addPrinter(printer.Name, api.port(), localDefaultDisplayName, "", p.gcpBaseURL, printer.GCPID, online) if err != nil { api.quit() return err @@ -102,7 +102,7 @@ func (p *Privet) UpdatePrinter(diff *lib.PrinterDiff) error { localDefaultDisplayName = fmt.Sprintf("%s (local)", localDefaultDisplayName) } - return p.zc.updatePrinterTXT(diff.Printer.GCPID, localDefaultDisplayName, p.gcpBaseURL, diff.Printer.GCPID, online) + return p.zc.updatePrinterTXT(diff.Printer.GCPID, localDefaultDisplayName, "", p.gcpBaseURL, diff.Printer.GCPID, online) } // DeletePrinter removes a printer from Privet. From 389f93a90dc4be7bc42fe742e671480ec4dbb1e3 Mon Sep 17 00:00:00 2001 From: Nick Westgate Date: Sun, 12 Jun 2016 05:29:11 +1200 Subject: [PATCH 15/76] Fix Windows build break in Privet from #237 (#249) Trivial fixes for: cups-connector\privet\privet.go:79: too many arguments in call to p.zc.addPrinter cups-connector\privet\privet.go:105: too many arguments in call to p.zc.updatePrinterTXT --- privet/windows.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/privet/windows.go b/privet/windows.go index af5e9d7..c9f9242 100644 --- a/privet/windows.go +++ b/privet/windows.go @@ -18,11 +18,11 @@ func newZeroconf() (*zeroconf, error) { return nil, errors.New("Privet has not been implemented for Windows") } -func (z *zeroconf) addPrinter(name string, port uint16, ty, url, id string, online bool) error { +func (z *zeroconf) addPrinter(name string, port uint16, ty, note, url, id string, online bool) error { return nil } -func (z *zeroconf) updatePrinterTXT(name, ty, url, id string, online bool) error { +func (z *zeroconf) updatePrinterTXT(name, ty, note, url, id string, online bool) error { return nil } From 7e23ca771abc5a463b4eaa98dc08a6a040228fc2 Mon Sep 17 00:00:00 2001 From: Jay Lee Date: Mon, 13 Jun 2016 12:20:34 -0700 Subject: [PATCH 16/76] Update group to cloud-print-connector@ --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 71ee725..3a3fc38 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ Share printers from your Windows, Linux, FreeBSD or OS X computer with ChromeOS Lots of help can be found in [the wiki](https://github.com/google/cups-connector/wiki). ## Mailing list -Please join the mailing list at https://groups.google.com/forum/#!forum/cups-connector. Anyone can post and view messages. +Please join the mailing list at https://groups.google.com/forum/#!forum/cloud-print-connector. Anyone can post and view messages. ## Build Status * Linux/OSX: [![Build Status](https://travis-ci.org/google/cups-connector.svg?branch=master)](https://travis-ci.org/google/cups-connector) From ea9f1d3a58955b1acaa57cdbe672595138fbe50c Mon Sep 17 00:00:00 2001 From: Adam Goode Date: Mon, 13 Jun 2016 14:45:32 -0400 Subject: [PATCH 17/76] Initial renaming from google/cups-connector to google/cloud-print-connector For issue #241. We still need to do a big rename of imports, but it should work ok for now. --- README.md | 2 +- lib/config.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 3a3fc38..b26605e 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ ## Introduction Share printers from your Windows, Linux, FreeBSD or OS X computer with ChromeOS and Android devices, using the Cloud Print Connector. The Connector is a purpose-built system process. It can share hundreds of printers on a powerful server, or one printer on a Raspberry Pi. -Lots of help can be found in [the wiki](https://github.com/google/cups-connector/wiki). +Lots of help can be found in [the wiki](https://github.com/google/cloud-print-connector/wiki). ## Mailing list Please join the mailing list at https://groups.google.com/forum/#!forum/cloud-print-connector. Anyone can post and view messages. diff --git a/lib/config.go b/lib/config.go index 480c258..dbf7d5e 100644 --- a/lib/config.go +++ b/lib/config.go @@ -22,7 +22,7 @@ const ( ConnectorName = "Cloud Print Connector" // A website with user-friendly information. - ConnectorHomeURL = "https://github.com/google/cups-connector" + ConnectorHomeURL = "https://github.com/google/cloud-print-connector" GCPAPIVersion = "2.0" ) From ecf66a5d5aa0839ef9a5abde8516d10976785922 Mon Sep 17 00:00:00 2001 From: Adam Goode Date: Mon, 13 Jun 2016 14:55:15 -0400 Subject: [PATCH 18/76] Update travis --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b26605e..300c12b 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ Lots of help can be found in [the wiki](https://github.com/google/cloud-print-co Please join the mailing list at https://groups.google.com/forum/#!forum/cloud-print-connector. Anyone can post and view messages. ## Build Status -* Linux/OSX: [![Build Status](https://travis-ci.org/google/cups-connector.svg?branch=master)](https://travis-ci.org/google/cups-connector) +* Linux/OSX: [![Build Status](https://travis-ci.org/google/cloud-print-connector.svg?branch=master)](https://travis-ci.org/google/cloud-print-connector) * FreeBSD: [![Build Status](http://jenkins.mouf.net/job/cups-connector/badge/icon)](http://jenkins.mouf.net/job/cups-connector/) From 2f4e164b6cd99823a8401a4c140805bcda14aa01 Mon Sep 17 00:00:00 2001 From: Adam Goode Date: Mon, 13 Jun 2016 15:27:24 -0400 Subject: [PATCH 19/76] Update jenkins --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 300c12b..8b568d3 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ Please join the mailing list at https://groups.google.com/forum/#!forum/cloud-pr ## Build Status * Linux/OSX: [![Build Status](https://travis-ci.org/google/cloud-print-connector.svg?branch=master)](https://travis-ci.org/google/cloud-print-connector) -* FreeBSD: [![Build Status](http://jenkins.mouf.net/job/cups-connector/badge/icon)](http://jenkins.mouf.net/job/cups-connector/) +* FreeBSD: [![Build Status](http://jenkins.mouf.net/job/cloud-print-connector/badge/icon)](http://jenkins.mouf.net/job/cloud-print-connector/) ## License Copyright 2015 Google Inc. All rights reserved. From 70dfe26a8229ce64831e365486a7ce0a62603544 Mon Sep 17 00:00:00 2001 From: Nick Westgate Date: Thu, 16 Jun 2016 03:26:16 +1200 Subject: [PATCH 20/76] Add util flags: gcp-oauth-client-id, gcp-oauth-client-secret (#257) Added new flags for GCPOAuthClientID and GCPOAuthClientSecret. Replaced references to these in lib.DefaultConfig with context.String(...). --- gcp-connector-util/init.go | 35 +++++++++++++++++++----------- gcp-connector-util/main_unix.go | 4 ++-- gcp-connector-util/main_windows.go | 4 ++-- 3 files changed, 26 insertions(+), 17 deletions(-) diff --git a/gcp-connector-util/init.go b/gcp-connector-util/init.go index c628581..66c0c53 100644 --- a/gcp-connector-util/init.go +++ b/gcp-connector-util/init.go @@ -38,7 +38,16 @@ var commonInitFlags = []cli.Flag{ Usage: "GCP API timeout, for debugging", Value: 30 * time.Second, }, - + cli.StringFlag{ + Name: "gcp-oauth-client-id", + Usage: "Identifies the CUPS Connector to the Google Cloud Print cloud service", + Value: lib.DefaultConfig.GCPOAuthClientID, + }, + cli.StringFlag{ + Name: "gcp-oauth-client-secret", + Usage: "Goes along with the Client ID. Not actually secret", + Value: lib.DefaultConfig.GCPOAuthClientSecret, + }, cli.StringFlag{ Name: "gcp-user-refresh-token", Usage: "GCP user refresh token, useful when managing many connectors", @@ -119,7 +128,7 @@ var commonInitFlags = []cli.Flag{ // https://developers.google.com/identity/protocols/OAuth2ForDevices func getUserClientFromUser(context *cli.Context) (*http.Client, string) { form := url.Values{ - "client_id": {lib.DefaultConfig.GCPOAuthClientID}, + "client_id": {context.String("gcp-oauth-client-id")}, "scope": {gcp.ScopeCloudPrint}, } response, err := http.PostForm(gcpOAuthDeviceCodeURL, form) @@ -144,8 +153,8 @@ func getUserClientFromUser(context *cli.Context) (*http.Client, string) { func pollOAuthConfirmation(context *cli.Context, deviceCode string, interval int) (*http.Client, string) { config := oauth2.Config{ - ClientID: lib.DefaultConfig.GCPOAuthClientID, - ClientSecret: lib.DefaultConfig.GCPOAuthClientSecret, + ClientID: context.String("gcp-oauth-client-id"), + ClientSecret: context.String("gcp-oauth-client-secret"), Endpoint: oauth2.Endpoint{ AuthURL: lib.DefaultConfig.GCPOAuthAuthURL, TokenURL: lib.DefaultConfig.GCPOAuthTokenURL, @@ -158,8 +167,8 @@ func pollOAuthConfirmation(context *cli.Context, deviceCode string, interval int time.Sleep(time.Duration(interval) * time.Second) form := url.Values{ - "client_id": {lib.DefaultConfig.GCPOAuthClientID}, - "client_secret": {lib.DefaultConfig.GCPOAuthClientSecret}, + "client_id": {context.String("gcp-oauth-client-id")}, + "client_secret": {context.String("gcp-oauth-client-secret")}, "code": {deviceCode}, "grant_type": {gcpOAuthGrantTypeDevice}, } @@ -196,8 +205,8 @@ func pollOAuthConfirmation(context *cli.Context, deviceCode string, interval int // getUserClientFromToken creates a user client with just a refresh token. func getUserClientFromToken(context *cli.Context) *http.Client { config := &oauth2.Config{ - ClientID: lib.DefaultConfig.GCPOAuthClientID, - ClientSecret: lib.DefaultConfig.GCPOAuthClientSecret, + ClientID: context.String("gcp-oauth-client-id"), + ClientSecret: context.String("gcp-oauth-client-secret"), Endpoint: oauth2.Endpoint{ AuthURL: lib.DefaultConfig.GCPOAuthAuthURL, TokenURL: lib.DefaultConfig.GCPOAuthTokenURL, @@ -216,7 +225,7 @@ func getUserClientFromToken(context *cli.Context) *http.Client { // initRobotAccount creates a GCP robot account for this connector. func initRobotAccount(context *cli.Context, userClient *http.Client) (string, string) { params := url.Values{} - params.Set("oauth_client_id", lib.DefaultConfig.GCPOAuthClientID) + params.Set("oauth_client_id", context.String("gcp-oauth-client-id")) url := fmt.Sprintf("%s%s?%s", lib.DefaultConfig.GCPBaseURL, "createrobot", params.Encode()) response, err := userClient.Get(url) @@ -244,10 +253,10 @@ func initRobotAccount(context *cli.Context, userClient *http.Client) (string, st return robotInit.XMPPJID, robotInit.AuthCode } -func verifyRobotAccount(authCode string) string { +func verifyRobotAccount(context *cli.Context, authCode string) string { config := &oauth2.Config{ - ClientID: lib.DefaultConfig.GCPOAuthClientID, - ClientSecret: lib.DefaultConfig.GCPOAuthClientSecret, + ClientID: context.String("gcp-oauth-client-id"), + ClientSecret: context.String("gcp-oauth-client-secret"), Endpoint: oauth2.Endpoint{ AuthURL: lib.DefaultConfig.GCPOAuthAuthURL, TokenURL: lib.DefaultConfig.GCPOAuthTokenURL, @@ -266,7 +275,7 @@ func verifyRobotAccount(authCode string) string { func createRobotAccount(context *cli.Context, userClient *http.Client) (string, string) { xmppJID, authCode := initRobotAccount(context, userClient) - token := verifyRobotAccount(authCode) + token := verifyRobotAccount(context, authCode) return xmppJID, token } diff --git a/gcp-connector-util/main_unix.go b/gcp-connector-util/main_unix.go index 2c08cb6..1a1aced 100644 --- a/gcp-connector-util/main_unix.go +++ b/gcp-connector-util/main_unix.go @@ -125,8 +125,8 @@ func createCloudConfig(context *cli.Context, xmppJID, robotRefreshToken, userRef XMPPPingTimeout: context.String("xmpp-ping-timeout"), XMPPPingInterval: context.String("xmpp-ping-interval"), GCPBaseURL: lib.DefaultConfig.GCPBaseURL, - GCPOAuthClientID: lib.DefaultConfig.GCPOAuthClientID, - GCPOAuthClientSecret: lib.DefaultConfig.GCPOAuthClientSecret, + GCPOAuthClientID: context.String("gcp-oauth-client-id"), + GCPOAuthClientSecret: context.String("gcp-oauth-client-secret"), GCPOAuthAuthURL: lib.DefaultConfig.GCPOAuthAuthURL, GCPOAuthTokenURL: lib.DefaultConfig.GCPOAuthTokenURL, GCPMaxConcurrentDownloads: uint(context.Int("gcp-max-concurrent-downloads")), diff --git a/gcp-connector-util/main_windows.go b/gcp-connector-util/main_windows.go index 6c38542..b7a1c1a 100644 --- a/gcp-connector-util/main_windows.go +++ b/gcp-connector-util/main_windows.go @@ -213,8 +213,8 @@ func createCloudConfig(context *cli.Context, xmppJID, robotRefreshToken, userRef XMPPPingTimeout: context.String("xmpp-ping-timeout"), XMPPPingInterval: context.String("xmpp-ping-interval"), GCPBaseURL: lib.DefaultConfig.GCPBaseURL, - GCPOAuthClientID: lib.DefaultConfig.GCPOAuthClientID, - GCPOAuthClientSecret: lib.DefaultConfig.GCPOAuthClientSecret, + GCPOAuthClientID: context.String("gcp-oauth-client-id"), + GCPOAuthClientSecret: context.String("gcp-oauth-client-secret"), GCPOAuthAuthURL: lib.DefaultConfig.GCPOAuthAuthURL, GCPOAuthTokenURL: lib.DefaultConfig.GCPOAuthTokenURL, GCPMaxConcurrentDownloads: uint(context.Int("gcp-max-concurrent-downloads")), From 8ef57e09ea1b8ffb76f5d2681e3ebe5a46f7bed2 Mon Sep 17 00:00:00 2001 From: Adam Goode Date: Wed, 15 Jun 2016 12:05:24 -0400 Subject: [PATCH 21/76] Rename cups-connector to cloud-print-connector (#253) * Rename to cloud-print-connector in import paths and other places * Remove CUPS from identifier in windows installer file * Rename temp files to be consistent with new name * Update app test in main_unix.go to match new name * Rename default socket and log to new name --- cups/core.go | 4 ++-- cups/cups.go | 20 +++++++++---------- cups/ppdcache.go | 2 +- cups/translate-attrs.go | 4 ++-- cups/translate-attrs_test.go | 4 ++-- cups/translate-ppd.go | 4 ++-- cups/translate-ppd_test.go | 2 +- cups/translate-ticket.go | 4 ++-- cups/translate-ticket_test.go | 4 ++-- gcp-connector-util/gcp-cups-connector-util.go | 6 +++--- gcp-connector-util/init.go | 4 ++-- gcp-connector-util/main_unix.go | 6 +++--- gcp-connector-util/main_windows.go | 2 +- gcp-connector-util/monitor.go | 2 +- gcp-cups-connector/gcp-cups-connector.go | 16 +++++++-------- .../gcp-windows-connector.go | 12 +++++------ gcp/gcp.go | 8 ++++---- gcp/http.go | 2 +- gcp/job.go | 2 +- lib/config.go | 2 +- lib/config_unix.go | 4 ++-- lib/job.go | 2 +- lib/printer.go | 2 +- log/log_windows.go | 2 +- manager/printermanager.go | 12 +++++------ monitor/monitor.go | 12 +++++------ privet/api-server.go | 8 ++++---- privet/avahi.go | 2 +- privet/bonjour.go | 2 +- privet/jobcache.go | 4 ++-- privet/privet.go | 2 +- winspool/winspool.go | 4 ++-- wix/windows-connector.wxs | 10 +++++----- xmpp/internal-xmpp.go | 2 +- xmpp/xmpp.go | 2 +- xmpp/xmpp_test.go | 2 +- 36 files changed, 91 insertions(+), 91 deletions(-) diff --git a/cups/core.go b/cups/core.go index 0e04cdc..f9ac5f7 100644 --- a/cups/core.go +++ b/cups/core.go @@ -23,8 +23,8 @@ import ( "time" "unsafe" - "github.com/google/cups-connector/lib" - "github.com/google/cups-connector/log" + "github.com/google/cloud-print-connector/lib" + "github.com/google/cloud-print-connector/log" ) const ( diff --git a/cups/cups.go b/cups/cups.go index 52d2f55..0221c29 100644 --- a/cups/cups.go +++ b/cups/cups.go @@ -26,9 +26,9 @@ import ( "time" "unsafe" - "github.com/google/cups-connector/cdd" - "github.com/google/cups-connector/lib" - "github.com/google/cups-connector/log" + "github.com/google/cloud-print-connector/cdd" + "github.com/google/cloud-print-connector/lib" + "github.com/google/cloud-print-connector/log" ) const ( @@ -275,13 +275,13 @@ func (c *CUPS) filterBlacklistPrinters(printers []lib.Printer) []lib.Printer { // filterClassPrinters removes class printers from the slice. func filterClassPrinters(printers []lib.Printer) []lib.Printer { - result := make([]lib.Printer, 0, len(printers)) - for i := range printers { - if !lib.PrinterIsClass(printers[i]) { - result = append(result, printers[i]) - } - } - return result + result := make([]lib.Printer, 0, len(printers)) + for i := range printers { + if !lib.PrinterIsClass(printers[i]) { + result = append(result, printers[i]) + } + } + return result } // filterRawPrinters removes raw printers from the slice. diff --git a/cups/ppdcache.go b/cups/ppdcache.go index c801f34..3c6eb18 100644 --- a/cups/ppdcache.go +++ b/cups/ppdcache.go @@ -19,7 +19,7 @@ import ( "sync" "unsafe" - "github.com/google/cups-connector/cdd" + "github.com/google/cloud-print-connector/cdd" ) // This isn't really a cache, but an interface to CUPS' quirky PPD interface. diff --git a/cups/translate-attrs.go b/cups/translate-attrs.go index 317787f..012c58e 100644 --- a/cups/translate-attrs.go +++ b/cups/translate-attrs.go @@ -15,8 +15,8 @@ import ( "strconv" "strings" - "github.com/google/cups-connector/cdd" - "github.com/google/cups-connector/log" + "github.com/google/cloud-print-connector/cdd" + "github.com/google/cloud-print-connector/log" ) // translateAttrs extracts a PrinterDescriptionSection, PrinterStateSection, name, default diplay name, UUID, and tags from maps of tags (CUPS attributes) diff --git a/cups/translate-attrs_test.go b/cups/translate-attrs_test.go index 3549e8b..65cfc71 100644 --- a/cups/translate-attrs_test.go +++ b/cups/translate-attrs_test.go @@ -13,8 +13,8 @@ import ( "reflect" "testing" - "github.com/google/cups-connector/cdd" - "github.com/google/cups-connector/log" + "github.com/google/cloud-print-connector/cdd" + "github.com/google/cloud-print-connector/log" ) func TestGetUUID(t *testing.T) { diff --git a/cups/translate-ppd.go b/cups/translate-ppd.go index d905122..6fc8fb6 100644 --- a/cups/translate-ppd.go +++ b/cups/translate-ppd.go @@ -14,8 +14,8 @@ import ( "strconv" "strings" - "github.com/google/cups-connector/cdd" - "github.com/google/cups-connector/log" + "github.com/google/cloud-print-connector/cdd" + "github.com/google/cloud-print-connector/log" ) const ( diff --git a/cups/translate-ppd_test.go b/cups/translate-ppd_test.go index 738eda5..cdc96b2 100644 --- a/cups/translate-ppd_test.go +++ b/cups/translate-ppd_test.go @@ -13,7 +13,7 @@ import ( "reflect" "testing" - "github.com/google/cups-connector/cdd" + "github.com/google/cloud-print-connector/cdd" ) func translationTest(t *testing.T, ppd string, expected *cdd.PrinterDescriptionSection) { diff --git a/cups/translate-ticket.go b/cups/translate-ticket.go index c815c0a..861907e 100644 --- a/cups/translate-ticket.go +++ b/cups/translate-ticket.go @@ -15,8 +15,8 @@ import ( "strconv" "strings" - "github.com/google/cups-connector/cdd" - "github.com/google/cups-connector/lib" + "github.com/google/cloud-print-connector/cdd" + "github.com/google/cloud-print-connector/lib" ) var rVendorIDKeyValue = regexp.MustCompile( diff --git a/cups/translate-ticket_test.go b/cups/translate-ticket_test.go index d27c891..9053278 100644 --- a/cups/translate-ticket_test.go +++ b/cups/translate-ticket_test.go @@ -15,8 +15,8 @@ import ( "strings" "testing" - "github.com/google/cups-connector/cdd" - "github.com/google/cups-connector/lib" + "github.com/google/cloud-print-connector/cdd" + "github.com/google/cloud-print-connector/lib" ) func TestTranslateTicket(t *testing.T) { diff --git a/gcp-connector-util/gcp-cups-connector-util.go b/gcp-connector-util/gcp-cups-connector-util.go index bd358a9..0a9f3f3 100644 --- a/gcp-connector-util/gcp-cups-connector-util.go +++ b/gcp-connector-util/gcp-cups-connector-util.go @@ -17,9 +17,9 @@ import ( "sync" "github.com/codegangsta/cli" - "github.com/google/cups-connector/cdd" - "github.com/google/cups-connector/gcp" - "github.com/google/cups-connector/lib" + "github.com/google/cloud-print-connector/cdd" + "github.com/google/cloud-print-connector/gcp" + "github.com/google/cloud-print-connector/lib" ) var commonCommands = []cli.Command{ diff --git a/gcp-connector-util/init.go b/gcp-connector-util/init.go index 66c0c53..f5c55ea 100644 --- a/gcp-connector-util/init.go +++ b/gcp-connector-util/init.go @@ -20,8 +20,8 @@ import ( "time" "github.com/codegangsta/cli" - "github.com/google/cups-connector/gcp" - "github.com/google/cups-connector/lib" + "github.com/google/cloud-print-connector/gcp" + "github.com/google/cloud-print-connector/lib" "golang.org/x/oauth2" ) diff --git a/gcp-connector-util/main_unix.go b/gcp-connector-util/main_unix.go index 1a1aced..0cd74a1 100644 --- a/gcp-connector-util/main_unix.go +++ b/gcp-connector-util/main_unix.go @@ -14,7 +14,7 @@ import ( "time" "github.com/codegangsta/cli" - "github.com/google/cups-connector/lib" + "github.com/google/cloud-print-connector/lib" ) var unixInitFlags = []cli.Flag{ @@ -98,8 +98,8 @@ func main() { log.SetFlags(0) app := cli.NewApp() - app.Name = "gcp-cups-connector-util" - app.Usage = "Google Cloud Print CUPS Connector utility tools" + app.Name = "gcp-connector-util" + app.Usage = "Google Cloud Print Connector utility tools" app.Version = lib.BuildDate app.Flags = []cli.Flag{ lib.ConfigFilenameFlag, diff --git a/gcp-connector-util/main_windows.go b/gcp-connector-util/main_windows.go index b7a1c1a..2a979e6 100644 --- a/gcp-connector-util/main_windows.go +++ b/gcp-connector-util/main_windows.go @@ -15,7 +15,7 @@ import ( "path/filepath" "github.com/codegangsta/cli" - "github.com/google/cups-connector/lib" + "github.com/google/cloud-print-connector/lib" "golang.org/x/sys/windows/svc" "golang.org/x/sys/windows/svc/eventlog" "golang.org/x/sys/windows/svc/mgr" diff --git a/gcp-connector-util/monitor.go b/gcp-connector-util/monitor.go index 08754b1..52aaf24 100644 --- a/gcp-connector-util/monitor.go +++ b/gcp-connector-util/monitor.go @@ -17,7 +17,7 @@ import ( "time" "github.com/codegangsta/cli" - "github.com/google/cups-connector/lib" + "github.com/google/cloud-print-connector/lib" ) func monitorConnector(context *cli.Context) { diff --git a/gcp-cups-connector/gcp-cups-connector.go b/gcp-cups-connector/gcp-cups-connector.go index c40b382..d6a2d19 100644 --- a/gcp-cups-connector/gcp-cups-connector.go +++ b/gcp-cups-connector/gcp-cups-connector.go @@ -20,14 +20,14 @@ import ( "github.com/codegangsta/cli" "github.com/coreos/go-systemd/journal" - "github.com/google/cups-connector/cups" - "github.com/google/cups-connector/gcp" - "github.com/google/cups-connector/lib" - "github.com/google/cups-connector/log" - "github.com/google/cups-connector/manager" - "github.com/google/cups-connector/monitor" - "github.com/google/cups-connector/privet" - "github.com/google/cups-connector/xmpp" + "github.com/google/cloud-print-connector/cups" + "github.com/google/cloud-print-connector/gcp" + "github.com/google/cloud-print-connector/lib" + "github.com/google/cloud-print-connector/log" + "github.com/google/cloud-print-connector/manager" + "github.com/google/cloud-print-connector/monitor" + "github.com/google/cloud-print-connector/privet" + "github.com/google/cloud-print-connector/xmpp" ) func main() { diff --git a/gcp-windows-connector/gcp-windows-connector.go b/gcp-windows-connector/gcp-windows-connector.go index d2dbc7a..7a488ae 100644 --- a/gcp-windows-connector/gcp-windows-connector.go +++ b/gcp-windows-connector/gcp-windows-connector.go @@ -15,12 +15,12 @@ import ( "time" "github.com/codegangsta/cli" - "github.com/google/cups-connector/gcp" - "github.com/google/cups-connector/lib" - "github.com/google/cups-connector/log" - "github.com/google/cups-connector/manager" - "github.com/google/cups-connector/winspool" - "github.com/google/cups-connector/xmpp" + "github.com/google/cloud-print-connector/gcp" + "github.com/google/cloud-print-connector/lib" + "github.com/google/cloud-print-connector/log" + "github.com/google/cloud-print-connector/manager" + "github.com/google/cloud-print-connector/winspool" + "github.com/google/cloud-print-connector/xmpp" "golang.org/x/sys/windows/svc" "golang.org/x/sys/windows/svc/debug" ) diff --git a/gcp/gcp.go b/gcp/gcp.go index 78b9b9f..011adca 100644 --- a/gcp/gcp.go +++ b/gcp/gcp.go @@ -26,9 +26,9 @@ import ( "golang.org/x/oauth2" - "github.com/google/cups-connector/cdd" - "github.com/google/cups-connector/lib" - "github.com/google/cups-connector/log" + "github.com/google/cloud-print-connector/cdd" + "github.com/google/cloud-print-connector/lib" + "github.com/google/cloud-print-connector/log" ) const ( @@ -703,7 +703,7 @@ func (gcp *GoogleCloudPrint) assembleJob(job *Job) (*cdd.CloudJobTicket, string, } } - file, err := ioutil.TempFile("", "cups-connector-gcp-") + file, err := ioutil.TempFile("", "cloud-print-connector-") if err != nil { return nil, "", fmt.Sprintf("Failed to create a temporary file: %s", err), diff --git a/gcp/http.go b/gcp/http.go index 99aeb7d..29364db 100644 --- a/gcp/http.go +++ b/gcp/http.go @@ -16,7 +16,7 @@ import ( "net/url" "strings" - "github.com/google/cups-connector/lib" + "github.com/google/cloud-print-connector/lib" "golang.org/x/oauth2" ) diff --git a/gcp/job.go b/gcp/job.go index cc94f39..0b03104 100644 --- a/gcp/job.go +++ b/gcp/job.go @@ -8,7 +8,7 @@ https://developers.google.com/open-source/licenses/bsd package gcp -import "github.com/google/cups-connector/cdd" +import "github.com/google/cloud-print-connector/cdd" type Job struct { GCPPrinterID string diff --git a/lib/config.go b/lib/config.go index dbf7d5e..09f4923 100644 --- a/lib/config.go +++ b/lib/config.go @@ -35,7 +35,7 @@ var ( } // To be populated by something like: - // go install -ldflags "-X github.com/google/cups-connector/lib.BuildDate=`date +%Y.%m.%d`" + // go install -ldflags "-X github.com/google/cloud-print-connector/lib.BuildDate=`date +%Y.%m.%d`" BuildDate = "DEV" ShortName = platformName + " Connector " + BuildDate + "-" + runtime.GOOS diff --git a/lib/config_unix.go b/lib/config_unix.go index 2acca50..177b7a3 100644 --- a/lib/config_unix.go +++ b/lib/config_unix.go @@ -171,12 +171,12 @@ var DefaultConfig = Config{ LocalPortLow: 26000, LocalPortHigh: 26999, - LogFileName: "/tmp/cups-connector", + LogFileName: "/tmp/cloud-print-connector", LogFileMaxMegabytes: 1, LogMaxFiles: 3, LogToJournal: PointerToBool(false), - MonitorSocketFilename: "/tmp/cups-connector-monitor.sock", + MonitorSocketFilename: "/tmp/cloud-print-connector-monitor.sock", CUPSMaxConnections: 50, CUPSConnectTimeout: "5s", diff --git a/lib/job.go b/lib/job.go index c86f03d..fe34874 100644 --- a/lib/job.go +++ b/lib/job.go @@ -8,7 +8,7 @@ https://developers.google.com/open-source/licenses/bsd package lib -import "github.com/google/cups-connector/cdd" +import "github.com/google/cloud-print-connector/cdd" type Job struct { NativePrinterName string diff --git a/lib/printer.go b/lib/printer.go index 509a939..2a1df0c 100644 --- a/lib/printer.go +++ b/lib/printer.go @@ -14,7 +14,7 @@ import ( "reflect" "regexp" - "github.com/google/cups-connector/cdd" + "github.com/google/cloud-print-connector/cdd" ) type PrinterState uint8 diff --git a/log/log_windows.go b/log/log_windows.go index 3d28670..8194030 100644 --- a/log/log_windows.go +++ b/log/log_windows.go @@ -12,7 +12,7 @@ package log import ( "fmt" - "github.com/google/cups-connector/lib" + "github.com/google/cloud-print-connector/lib" "golang.org/x/sys/windows/svc/debug" "golang.org/x/sys/windows/svc/eventlog" ) diff --git a/manager/printermanager.go b/manager/printermanager.go index 5b842c8..20f893f 100644 --- a/manager/printermanager.go +++ b/manager/printermanager.go @@ -17,12 +17,12 @@ import ( "sync" "time" - "github.com/google/cups-connector/cdd" - "github.com/google/cups-connector/gcp" - "github.com/google/cups-connector/lib" - "github.com/google/cups-connector/log" - "github.com/google/cups-connector/privet" - "github.com/google/cups-connector/xmpp" + "github.com/google/cloud-print-connector/cdd" + "github.com/google/cloud-print-connector/gcp" + "github.com/google/cloud-print-connector/lib" + "github.com/google/cloud-print-connector/log" + "github.com/google/cloud-print-connector/privet" + "github.com/google/cloud-print-connector/xmpp" ) type NativePrintSystem interface { diff --git a/monitor/monitor.go b/monitor/monitor.go index ca043d7..49f86b0 100644 --- a/monitor/monitor.go +++ b/monitor/monitor.go @@ -12,12 +12,12 @@ import ( "fmt" "net" - "github.com/google/cups-connector/cups" - "github.com/google/cups-connector/gcp" - "github.com/google/cups-connector/lib" - "github.com/google/cups-connector/log" - "github.com/google/cups-connector/manager" - "github.com/google/cups-connector/privet" + "github.com/google/cloud-print-connector/cups" + "github.com/google/cloud-print-connector/gcp" + "github.com/google/cloud-print-connector/lib" + "github.com/google/cloud-print-connector/log" + "github.com/google/cloud-print-connector/manager" + "github.com/google/cloud-print-connector/privet" ) const monitorFormat = `cups-printers=%d diff --git a/privet/api-server.go b/privet/api-server.go index 5033a73..f10aa6d 100644 --- a/privet/api-server.go +++ b/privet/api-server.go @@ -20,9 +20,9 @@ import ( "strings" "time" - "github.com/google/cups-connector/cdd" - "github.com/google/cups-connector/lib" - "github.com/google/cups-connector/log" + "github.com/google/cloud-print-connector/cdd" + "github.com/google/cloud-print-connector/lib" + "github.com/google/cloud-print-connector/log" ) var ( @@ -381,7 +381,7 @@ func (api *privetAPI) submitdoc(w http.ResponseWriter, r *http.Request) { return } - file, err := ioutil.TempFile("", "cups-connector-privet-") + file, err := ioutil.TempFile("", "cloud-print-connector-privet-") if err != nil { log.Errorf("Failed to create file for new Privet job: %s", err) w.WriteHeader(http.StatusInternalServerError) diff --git a/privet/avahi.go b/privet/avahi.go index 64778b2..2ad075e 100644 --- a/privet/avahi.go +++ b/privet/avahi.go @@ -19,7 +19,7 @@ import ( "sync" "unsafe" - "github.com/google/cups-connector/log" + "github.com/google/cloud-print-connector/log" ) var ( diff --git a/privet/bonjour.go b/privet/bonjour.go index 1dc78f5..bc64625 100644 --- a/privet/bonjour.go +++ b/privet/bonjour.go @@ -17,7 +17,7 @@ import ( "sync" "unsafe" - "github.com/google/cups-connector/log" + "github.com/google/cloud-print-connector/log" ) // TODO: How to add the _printer subtype? diff --git a/privet/jobcache.go b/privet/jobcache.go index 76009b1..79b222e 100644 --- a/privet/jobcache.go +++ b/privet/jobcache.go @@ -14,8 +14,8 @@ import ( "sync" "time" - "github.com/google/cups-connector/cdd" - "github.com/google/cups-connector/log" + "github.com/google/cloud-print-connector/cdd" + "github.com/google/cloud-print-connector/log" ) // Jobs expire after this much time. diff --git a/privet/privet.go b/privet/privet.go index d42b659..723ccf3 100644 --- a/privet/privet.go +++ b/privet/privet.go @@ -12,7 +12,7 @@ import ( "fmt" "sync" - "github.com/google/cups-connector/lib" + "github.com/google/cloud-print-connector/lib" ) // Privet managers local discovery and printing. diff --git a/winspool/winspool.go b/winspool/winspool.go index 907620b..3fbba08 100644 --- a/winspool/winspool.go +++ b/winspool/winspool.go @@ -16,8 +16,8 @@ import ( "strconv" "strings" - "github.com/google/cups-connector/cdd" - "github.com/google/cups-connector/lib" + "github.com/google/cloud-print-connector/cdd" + "github.com/google/cloud-print-connector/lib" ) // winspoolPDS represents capabilities that WinSpool always provides. diff --git a/wix/windows-connector.wxs b/wix/windows-connector.wxs index 58f6ea3..4614c60 100644 --- a/wix/windows-connector.wxs +++ b/wix/windows-connector.wxs @@ -44,10 +44,10 @@ - + - + @@ -64,9 +64,9 @@ INITCMD NOT NO_INSTALL_SERVICE AND NOT Installed - $CUPS_Connector_exe=3 AND NOT (NO_INSTALL_SERVICE OR NO_START_SERVICE) - ?CUPS_Connector_exe=3 - $CUPS_Connector_exe=2 + $Cloud_Print_Connector_exe=3 AND NOT (NO_INSTALL_SERVICE OR NO_START_SERVICE) + ?Cloud_Print_Connector_exe=3 + $Cloud_Print_Connector_exe=2 diff --git a/xmpp/internal-xmpp.go b/xmpp/internal-xmpp.go index 0e7ac75..b2d28d6 100644 --- a/xmpp/internal-xmpp.go +++ b/xmpp/internal-xmpp.go @@ -24,7 +24,7 @@ import ( "strings" "time" - "github.com/google/cups-connector/log" + "github.com/google/cloud-print-connector/log" ) const ( diff --git a/xmpp/xmpp.go b/xmpp/xmpp.go index 84f1f6e..6853843 100644 --- a/xmpp/xmpp.go +++ b/xmpp/xmpp.go @@ -12,7 +12,7 @@ import ( "fmt" "time" - "github.com/google/cups-connector/log" + "github.com/google/cloud-print-connector/log" ) type PrinterNotificationType uint8 diff --git a/xmpp/xmpp_test.go b/xmpp/xmpp_test.go index 0396696..650c15b 100644 --- a/xmpp/xmpp_test.go +++ b/xmpp/xmpp_test.go @@ -24,7 +24,7 @@ import ( "testing" "time" - "github.com/google/cups-connector/xmpp" + "github.com/google/cloud-print-connector/xmpp" ) func TestXMPP_proxyauth(t *testing.T) { From 2d951073d8ffe48f78f9cb64413310d6a644d679 Mon Sep 17 00:00:00 2001 From: Adam Goode Date: Wed, 15 Jun 2016 16:04:37 -0400 Subject: [PATCH 22/76] Add exponential backoff and use it (#250) * Add lib.Backoff for doing exponential backoff Inspired from: https://en.wikipedia.org/wiki/Exponential_backoff https://github.com/google/google-api-go-client/blob/master/gensupport/backoff.go https://developers.google.com/api-client-library/java/google-http-java-client/reference/1.20.0/com/google/api/client/util/ExponentialBackOff * Change postWithRetry and getWithRetry to use exponential backoff * Use exponential backoff in gcp-connector-util init --- gcp-connector-util/init.go | 22 +++++++++++++-- gcp/http.go | 44 +++++++++++++++++++++--------- lib/backoff.go | 55 ++++++++++++++++++++++++++++++++++++++ lib/backoff_test.go | 47 ++++++++++++++++++++++++++++++++ 4 files changed, 154 insertions(+), 14 deletions(-) create mode 100644 lib/backoff.go create mode 100644 lib/backoff_test.go diff --git a/gcp-connector-util/init.go b/gcp-connector-util/init.go index f5c55ea..be5798b 100644 --- a/gcp-connector-util/init.go +++ b/gcp-connector-util/init.go @@ -124,6 +124,24 @@ var commonInitFlags = []cli.Flag{ }, } +func postWithRetry(url string, data url.Values) (*http.Response, error) { + backoff := lib.Backoff{} + for { + response, err := http.PostForm(url, data) + if err == nil { + return response, err + } + fmt.Printf("POST to %s failed with error: %s\n", url, err) + + p, retryAgain := backoff.Pause() + if !retryAgain { + return response, err + } + fmt.Printf("retrying POST to %s in %s\n", url, p) + time.Sleep(p) + } +} + // getUserClientFromUser follows the token acquisition steps outlined here: // https://developers.google.com/identity/protocols/OAuth2ForDevices func getUserClientFromUser(context *cli.Context) (*http.Client, string) { @@ -131,7 +149,7 @@ func getUserClientFromUser(context *cli.Context) (*http.Client, string) { "client_id": {context.String("gcp-oauth-client-id")}, "scope": {gcp.ScopeCloudPrint}, } - response, err := http.PostForm(gcpOAuthDeviceCodeURL, form) + response, err := postWithRetry(gcpOAuthDeviceCodeURL, form) if err != nil { log.Fatalln(err) } @@ -172,7 +190,7 @@ func pollOAuthConfirmation(context *cli.Context, deviceCode string, interval int "code": {deviceCode}, "grant_type": {gcpOAuthGrantTypeDevice}, } - response, err := http.PostForm(gcpOAuthTokenPollURL, form) + response, err := postWithRetry(gcpOAuthTokenPollURL, form) if err != nil { log.Fatalln(err) } diff --git a/gcp/http.go b/gcp/http.go index 29364db..b4ea629 100644 --- a/gcp/http.go +++ b/gcp/http.go @@ -15,8 +15,10 @@ import ( "net/http" "net/url" "strings" + "time" "github.com/google/cloud-print-connector/lib" + "github.com/google/cloud-print-connector/log" "golang.org/x/oauth2" ) @@ -58,15 +60,24 @@ func newClient(oauthClientID, oauthClientSecret, oauthAuthURL, oauthTokenURL, re return client, nil } -// getWithRetry calls get() and retries once on HTTP failure +// getWithRetry calls get() and retries on HTTP failure // (response code != 200). func getWithRetry(hc *http.Client, url string) (*http.Response, error) { - response, err := get(hc, url) - if response != nil && response.StatusCode == http.StatusOK { - return response, err + backoff := lib.Backoff{} + for { + response, err := get(hc, url) + if response != nil && response.StatusCode == http.StatusOK { + return response, err + } + + p, retryAgain := backoff.Pause() + if !retryAgain { + log.Debugf("HTTP error %s, retry timeout hit", err, p) + return response, err + } + log.Debugf("HTTP error %s, retrying after %s", err, p) + time.Sleep(p) } - - return get(hc, url) } // get GETs a URL. Returns the response object (not body), in case the body @@ -93,15 +104,24 @@ func get(hc *http.Client, url string) (*http.Response, error) { return response, nil } -// postWithRetry calls post() and retries once on HTTP failure +// postWithRetry calls post() and retries on HTTP failure // (response code != 200). func postWithRetry(hc *http.Client, url string, form url.Values) ([]byte, uint, int, error) { - responseBody, gcpErrorCode, httpStatusCode, err := post(hc, url, form) - if responseBody != nil && httpStatusCode == http.StatusOK { - return responseBody, gcpErrorCode, httpStatusCode, err + backoff := lib.Backoff{} + for { + responseBody, gcpErrorCode, httpStatusCode, err := post(hc, url, form) + if responseBody != nil && httpStatusCode == http.StatusOK { + return responseBody, gcpErrorCode, httpStatusCode, err + } + + p, retryAgain := backoff.Pause() + if !retryAgain { + log.Debugf("HTTP error %s, retry timeout hit", err, p) + return responseBody, gcpErrorCode, httpStatusCode, err + } + log.Debugf("HTTP error %s, retrying after %s", err, p) + time.Sleep(p) } - - return post(hc, url, form) } // post POSTs to a URL. Returns the body of the response. diff --git a/lib/backoff.go b/lib/backoff.go new file mode 100644 index 0000000..e34bd54 --- /dev/null +++ b/lib/backoff.go @@ -0,0 +1,55 @@ +/* +Copyright 2016 Google Inc. All rights reserved. + +Use of this source code is governed by a BSD-style +license that can be found in the LICENSE file or at +https://developers.google.com/open-source/licenses/bsd +*/ + +package lib + +import ( + "math/rand" + "time" +) + +const ( + initialRetryInterval = 500 * time.Millisecond + maxInterval = 1 * time.Minute + maxElapsedTime = 15 * time.Minute + multiplier = 1.5 + randomizationFactor = 0.5 +) + +// Backoff provides a mechanism for determining a good amount of time before +// retrying an operation. +type Backoff struct { + interval time.Duration + elapsedTime time.Duration +} + +// Pause returns the amount of time to wait before retrying an operation and true if +// it is ok to try again or false if the operation should be abandoned. +func (b *Backoff) Pause() (time.Duration, bool) { + if b.interval == 0 { + // first time + b.interval = initialRetryInterval + b.elapsedTime = 0 + } + + // interval from [1 - randomizationFactor, 1 + randomizationFactor) + randomizedInterval := time.Duration((rand.Float64()*(2*randomizationFactor) + (1 - randomizationFactor)) * float64(b.interval)) + b.elapsedTime += randomizedInterval + + if b.elapsedTime > maxElapsedTime { + return 0, false + } + + // Increase interval up to the interval cap + b.interval = time.Duration(float64(b.interval) * multiplier) + if b.interval > maxInterval { + b.interval = maxInterval + } + + return randomizedInterval, true +} diff --git a/lib/backoff_test.go b/lib/backoff_test.go new file mode 100644 index 0000000..5f0c091 --- /dev/null +++ b/lib/backoff_test.go @@ -0,0 +1,47 @@ +/* +Copyright 2016 Google Inc. All rights reserved. + +Use of this source code is governed by a BSD-style +license that can be found in the LICENSE file or at +https://developers.google.com/open-source/licenses/bsd +*/ + +package lib + +import ( + "testing" + "time" +) + +func TestBackoffMultiple(t *testing.T) { + b := &Backoff{} + // with the current parameters, we will be able to wait at least 19 times before hitting the max + for i := 0; i < 19; i++ { + p, ok := b.Pause() + t.Logf("iteration %d pausing for %s", i, p) + if !ok { + t.Fatalf("hit the pause timeout after %d pauses", i) + } + } +} + +func TestBackoffTimeout(t *testing.T) { + var elapsed time.Duration + b := &Backoff{} + // with the current parameters, we will hit the timeout at or before 40 pauses + for i := 0; i < 40; i++ { + p, ok := b.Pause() + elapsed += p + t.Logf("iteration %d pausing for %s (total %s)", i, p, elapsed) + if !ok { + break + } + } + if _, ok := b.Pause(); ok { + t.Fatalf("did not hit the pause timeout") + } + + if elapsed > maxElapsedTime { + t.Fatalf("waited too long: %s > %s", elapsed, maxElapsedTime) + } +} From 1fe98ab8b98394aece2f3196c0b48f90495a2c1a Mon Sep 17 00:00:00 2001 From: Adam Goode Date: Wed, 15 Jun 2016 16:06:03 -0400 Subject: [PATCH 23/76] Ensure we don't forget about Color and Duplex options on restart (#256) The CDD struct was overloaded with some local fields, and these were getting lost during synchronization if we did a clean restart. To ensure the diff/sync logic is simple, we don't overload CDD anymore. Instead, we pack key:value pairs into CDD vendor fields when possible and add fields to Printer when not. --- cdd/cdd.go | 7 +- cups/cups.go | 5 +- cups/ppdcache.go | 25 ++-- cups/translate-attrs.go | 11 +- cups/translate-attrs_test.go | 9 +- cups/translate-ppd.go | 50 ++++--- cups/translate-ppd_test.go | 245 ++++++++++++++++++++-------------- cups/translate-ticket.go | 20 ++- cups/translate-ticket_test.go | 34 +++-- lib/printer.go | 19 +-- 10 files changed, 246 insertions(+), 179 deletions(-) diff --git a/cdd/cdd.go b/cdd/cdd.go index 5abc4b5..f44e276 100644 --- a/cdd/cdd.go +++ b/cdd/cdd.go @@ -316,8 +316,7 @@ type TypedValueCapability struct { } type Color struct { - Option []ColorOption `json:"option"` - VendorKey string `json:"-"` + Option []ColorOption `json:"option"` } type ColorType string @@ -339,8 +338,7 @@ type ColorOption struct { } type Duplex struct { - Option []DuplexOption `json:"option"` - VendorKey string `json:"-"` + Option []DuplexOption `json:"option"` } type DuplexType string @@ -354,7 +352,6 @@ const ( type DuplexOption struct { Type DuplexType `json:"type"` // default = "NO_DUPLEX" IsDefault bool `json:"is_default"` // default = false - VendorID string `json:"-"` } type PageOrientation struct { diff --git a/cups/cups.go b/cups/cups.go index 0221c29..e99493f 100644 --- a/cups/cups.go +++ b/cups/cups.go @@ -304,10 +304,13 @@ func (c *CUPS) addPPDDescriptionToPrinters(printers []lib.Printer) []lib.Printer for i := range printers { wg.Add(1) go func(p *lib.Printer) { - if description, manufacturer, model, err := c.pc.getPPDCacheEntry(p.Name); err == nil { + if description, manufacturer, model, duplexMap, err := c.pc.getPPDCacheEntry(p.Name); err == nil { p.Description.Absorb(description) p.Manufacturer = manufacturer p.Model = model + if duplexMap != nil { + p.DuplexMap = duplexMap + } ch <- p } else { log.ErrorPrinter(p.Name, err) diff --git a/cups/ppdcache.go b/cups/ppdcache.go index 3c6eb18..17f2151 100644 --- a/cups/ppdcache.go +++ b/cups/ppdcache.go @@ -20,6 +20,7 @@ import ( "unsafe" "github.com/google/cloud-print-connector/cdd" + "github.com/google/cloud-print-connector/lib" ) // This isn't really a cache, but an interface to CUPS' quirky PPD interface. @@ -65,7 +66,7 @@ func (pc *ppdCache) removePPD(printername string) { } } -func (pc *ppdCache) getPPDCacheEntry(printername string) (*cdd.PrinterDescriptionSection, string, string, error) { +func (pc *ppdCache) getPPDCacheEntry(printername string) (*cdd.PrinterDescriptionSection, string, string, lib.DuplexVendorMap, error) { pc.cacheMutex.RLock() pce, exists := pc.cache[printername] pc.cacheMutex.RUnlock() @@ -73,11 +74,11 @@ func (pc *ppdCache) getPPDCacheEntry(printername string) (*cdd.PrinterDescriptio if !exists { pce, err := createPPDCacheEntry(printername) if err != nil { - return nil, "", "", err + return nil, "", "", nil, err } if err = pce.refresh(pc.cc); err != nil { pce.free() - return nil, "", "", err + return nil, "", "", nil, err } pc.cacheMutex.Lock() @@ -89,17 +90,17 @@ func (pc *ppdCache) getPPDCacheEntry(printername string) (*cdd.PrinterDescriptio go firstPCE.free() } pc.cache[printername] = pce - description, manufacturer, model := pce.getFields() - return &description, manufacturer, model, nil + description, manufacturer, model, duplexMap := pce.getFields() + return &description, manufacturer, model, duplexMap, nil } else { if err := pce.refresh(pc.cc); err != nil { delete(pc.cache, printername) pce.free() - return nil, "", "", err + return nil, "", "", nil, err } - description, manufacturer, model := pce.getFields() - return &description, manufacturer, model, nil + description, manufacturer, model, duplexMap := pce.getFields() + return &description, manufacturer, model, duplexMap, nil } } @@ -110,6 +111,7 @@ type ppdCacheEntry struct { description cdd.PrinterDescriptionSection manufacturer string model string + duplexMap lib.DuplexVendorMap mutex sync.Mutex } @@ -127,10 +129,10 @@ func createPPDCacheEntry(name string) (*ppdCacheEntry, error) { // getFields gets externally-interesting fields from this ppdCacheEntry under // a lock. The description is passed as a value (copy), to protect the cached copy. -func (pce *ppdCacheEntry) getFields() (cdd.PrinterDescriptionSection, string, string) { +func (pce *ppdCacheEntry) getFields() (cdd.PrinterDescriptionSection, string, string, lib.DuplexVendorMap) { pce.mutex.Lock() defer pce.mutex.Unlock() - return pce.description, pce.manufacturer, pce.model + return pce.description, pce.manufacturer, pce.model, pce.duplexMap } // free frees the memory that stores the name and buffer fields, and deletes @@ -176,7 +178,7 @@ func (pce *ppdCacheEntry) refresh(cc *cupsCore) error { return err } - description, manufacturer, model := translatePPD(w.String()) + description, manufacturer, model, duplexMap := translatePPD(w.String()) if description == nil || manufacturer == "" || model == "" { return errors.New("Failed to parse PPD") } @@ -184,6 +186,7 @@ func (pce *ppdCacheEntry) refresh(cc *cupsCore) error { pce.description = *description pce.manufacturer = manufacturer pce.model = model + pce.duplexMap = duplexMap return nil } diff --git a/cups/translate-attrs.go b/cups/translate-attrs.go index 012c58e..74eeb83 100644 --- a/cups/translate-attrs.go +++ b/cups/translate-attrs.go @@ -434,17 +434,17 @@ func convertCopies(printerTags map[string][]string) *cdd.Copies { var colorByKeyword = map[string]cdd.ColorOption{ "auto": cdd.ColorOption{ - VendorID: "auto", + VendorID: attrPrintColorMode + internalKeySeparator + "auto", Type: cdd.ColorTypeAuto, CustomDisplayNameLocalized: cdd.NewLocalizedString("Auto"), }, "color": cdd.ColorOption{ - VendorID: "color", + VendorID: attrPrintColorMode + internalKeySeparator + "color", Type: cdd.ColorTypeStandardColor, CustomDisplayNameLocalized: cdd.NewLocalizedString("Color"), }, "monochrome": cdd.ColorOption{ - VendorID: "monochrome", + VendorID: attrPrintColorMode + internalKeySeparator + "monochrome", Type: cdd.ColorTypeStandardMonochrome, CustomDisplayNameLocalized: cdd.NewLocalizedString("Monochrome"), }, @@ -456,18 +456,19 @@ func convertColorAttrs(printerTags map[string][]string) *cdd.Color { return nil } + c := cdd.Color{} + colorDefault, exists := printerTags[attrPrintColorModeDefault] if !exists || len(colorDefault) != 1 { colorDefault = colorSupported[:1] } - c := cdd.Color{VendorKey: attrPrintColorMode} for _, color := range colorSupported { var co cdd.ColorOption var exists bool if co, exists = colorByKeyword[color]; !exists { co = cdd.ColorOption{ - VendorID: color, + VendorID: attrPrintColorMode + internalKeySeparator + color, Type: cdd.ColorTypeCustomColor, CustomDisplayNameLocalized: cdd.NewLocalizedString(color), } diff --git a/cups/translate-attrs_test.go b/cups/translate-attrs_test.go index 65cfc71..a418fc6 100644 --- a/cups/translate-attrs_test.go +++ b/cups/translate-attrs_test.go @@ -585,12 +585,11 @@ func TestConvertColorAttrs(t *testing.T) { } expected := &cdd.Color{ Option: []cdd.ColorOption{ - cdd.ColorOption{"color", cdd.ColorTypeStandardColor, "", false, cdd.NewLocalizedString("Color")}, - cdd.ColorOption{"monochrome", cdd.ColorTypeStandardMonochrome, "", false, cdd.NewLocalizedString("Monochrome")}, - cdd.ColorOption{"auto", cdd.ColorTypeAuto, "", true, cdd.NewLocalizedString("Auto")}, - cdd.ColorOption{"zebra", cdd.ColorTypeCustomColor, "", false, cdd.NewLocalizedString("zebra")}, + cdd.ColorOption{"print-color-mode:color", cdd.ColorTypeStandardColor, "", false, cdd.NewLocalizedString("Color")}, + cdd.ColorOption{"print-color-mode:monochrome", cdd.ColorTypeStandardMonochrome, "", false, cdd.NewLocalizedString("Monochrome")}, + cdd.ColorOption{"print-color-mode:auto", cdd.ColorTypeAuto, "", true, cdd.NewLocalizedString("Auto")}, + cdd.ColorOption{"print-color-mode:zebra", cdd.ColorTypeCustomColor, "", false, cdd.NewLocalizedString("zebra")}, }, - VendorKey: "print-color-mode", } c = convertColorAttrs(pt) if !reflect.DeepEqual(expected, c) { diff --git a/cups/translate-ppd.go b/cups/translate-ppd.go index 6fc8fb6..3371c90 100644 --- a/cups/translate-ppd.go +++ b/cups/translate-ppd.go @@ -15,6 +15,7 @@ import ( "strings" "github.com/google/cloud-print-connector/cdd" + "github.com/google/cloud-print-connector/lib" "github.com/google/cloud-print-connector/log" ) @@ -124,9 +125,9 @@ type entry struct { options []statement } -// translatePPD extracts a PrinterDescriptionSection, manufacturer string, and model string +// translatePPD extracts a PrinterDescriptionSection, manufacturer string, model string, and DuplexVendorMap // from a PPD string. -func translatePPD(ppd string) (*cdd.PrinterDescriptionSection, string, string) { +func translatePPD(ppd string) (*cdd.PrinterDescriptionSection, string, string, lib.DuplexVendorMap) { statements := ppdToStatements(ppd) openUIStatements, installables, uiConstraints, standAlones := groupStatements(statements) openUIStatements = filterConstraints(openUIStatements, installables, uiConstraints) @@ -135,6 +136,7 @@ func translatePPD(ppd string) (*cdd.PrinterDescriptionSection, string, string) { pds := cdd.PrinterDescriptionSection{ VendorCapability: &[]cdd.VendorCapability{}, } + var duplexMap lib.DuplexVendorMap if e, exists := entriesByMainKeyword[ppdPageSize]; exists { pds.MediaSize = convertMediaSize(e) } @@ -146,9 +148,9 @@ func translatePPD(ppd string) (*cdd.PrinterDescriptionSection, string, string) { pds.Color = convertColorPPD(e) } if e, exists := entriesByMainKeyword[ppdDuplex]; exists { - pds.Duplex = convertDuplex(e) + pds.Duplex, duplexMap = convertDuplex(e) } else if e, exists := entriesByMainKeyword[ppdKMDuplex]; exists { - pds.Duplex = convertDuplex(e) + pds.Duplex, duplexMap = convertDuplex(e) } if e, exists := entriesByMainKeyword[ppdResolution]; exists { pds.DPI = convertDPI(e) @@ -187,7 +189,7 @@ func translatePPD(ppd string) (*cdd.PrinterDescriptionSection, string, string) { } model = strings.TrimLeft(strings.TrimPrefix(model, manufacturer), " ") - return &pds, manufacturer, model + return &pds, manufacturer, model, duplexMap } // ppdToStatements converts a PPD file to a slice of statements. @@ -473,11 +475,11 @@ func convertColorPPD(e entry) *cdd.Color { } } - c := cdd.Color{VendorKey: e.mainKeyword} + c := cdd.Color{} if len(colorOptions) == 1 { colorName := cleanupColorName(colorOptions[0].optionKeyword, colorOptions[0].translation) co := cdd.ColorOption{ - VendorID: colorOptions[0].optionKeyword, + VendorID: e.mainKeyword + internalKeySeparator + colorOptions[0].optionKeyword, Type: cdd.ColorTypeStandardColor, CustomDisplayNameLocalized: cdd.NewLocalizedString(colorName), } @@ -486,7 +488,7 @@ func convertColorPPD(e entry) *cdd.Color { for _, o := range colorOptions { colorName := cleanupColorName(o.optionKeyword, o.translation) co := cdd.ColorOption{ - VendorID: o.optionKeyword, + VendorID: e.mainKeyword + internalKeySeparator + o.optionKeyword, Type: cdd.ColorTypeCustomColor, CustomDisplayNameLocalized: cdd.NewLocalizedString(colorName), } @@ -497,7 +499,7 @@ func convertColorPPD(e entry) *cdd.Color { if len(grayOptions) == 1 { colorName := cleanupColorName(grayOptions[0].optionKeyword, grayOptions[0].translation) co := cdd.ColorOption{ - VendorID: grayOptions[0].optionKeyword, + VendorID: e.mainKeyword + internalKeySeparator + grayOptions[0].optionKeyword, Type: cdd.ColorTypeStandardMonochrome, CustomDisplayNameLocalized: cdd.NewLocalizedString(colorName), } @@ -506,7 +508,7 @@ func convertColorPPD(e entry) *cdd.Color { for _, o := range grayOptions { colorName := cleanupColorName(o.optionKeyword, o.translation) co := cdd.ColorOption{ - VendorID: o.optionKeyword, + VendorID: e.mainKeyword + internalKeySeparator + o.optionKeyword, Type: cdd.ColorTypeCustomMonochrome, CustomDisplayNameLocalized: cdd.NewLocalizedString(colorName), } @@ -517,7 +519,7 @@ func convertColorPPD(e entry) *cdd.Color { for _, o := range otherOptions { colorName := cleanupColorName(o.optionKeyword, o.translation) co := cdd.ColorOption{ - VendorID: o.optionKeyword, + VendorID: e.mainKeyword + internalKeySeparator + o.optionKeyword, Type: cdd.ColorTypeCustomMonochrome, CustomDisplayNameLocalized: cdd.NewLocalizedString(colorName), } @@ -529,7 +531,7 @@ func convertColorPPD(e entry) *cdd.Color { } for i := range c.Option { - if c.Option[i].VendorID == e.defaultValue { + if c.Option[i].VendorID == e.mainKeyword+internalKeySeparator+e.defaultValue { c.Option[i].IsDefault = true return &c } @@ -539,41 +541,47 @@ func convertColorPPD(e entry) *cdd.Color { return &c } -func convertDuplex(e entry) *cdd.Duplex { - d := cdd.Duplex{VendorKey: e.mainKeyword} +func convertDuplex(e entry) (*cdd.Duplex, lib.DuplexVendorMap) { + d := cdd.Duplex{} + duplexMap := lib.DuplexVendorMap{} var foundDefault bool for _, o := range e.options { def := o.optionKeyword == e.defaultValue switch o.optionKeyword { case ppdNone, ppdFalse, ppdKMDuplexSingle: - d.Option = append(d.Option, cdd.DuplexOption{cdd.DuplexNoDuplex, def, o.optionKeyword}) + d.Option = append(d.Option, cdd.DuplexOption{cdd.DuplexNoDuplex, def}) + duplexMap[cdd.DuplexNoDuplex] = e.mainKeyword + internalKeySeparator + o.optionKeyword foundDefault = foundDefault || def case ppdDuplexNoTumble, ppdTrue, ppdKMDuplexDouble: - d.Option = append(d.Option, cdd.DuplexOption{cdd.DuplexLongEdge, def, o.optionKeyword}) + d.Option = append(d.Option, cdd.DuplexOption{cdd.DuplexLongEdge, def}) + duplexMap[cdd.DuplexLongEdge] = e.mainKeyword + internalKeySeparator + o.optionKeyword foundDefault = foundDefault || def case ppdDuplexTumble, ppdKMDuplexBooklet: - d.Option = append(d.Option, cdd.DuplexOption{cdd.DuplexShortEdge, def, o.optionKeyword}) + d.Option = append(d.Option, cdd.DuplexOption{cdd.DuplexShortEdge, def}) + duplexMap[cdd.DuplexShortEdge] = e.mainKeyword + internalKeySeparator + o.optionKeyword foundDefault = foundDefault || def default: if strings.HasPrefix(o.optionKeyword, "1") { - d.Option = append(d.Option, cdd.DuplexOption{cdd.DuplexNoDuplex, def, o.optionKeyword}) + d.Option = append(d.Option, cdd.DuplexOption{cdd.DuplexNoDuplex, def}) + duplexMap[cdd.DuplexNoDuplex] = e.mainKeyword + internalKeySeparator + o.optionKeyword foundDefault = foundDefault || def } else if strings.HasPrefix(o.optionKeyword, "2") { - d.Option = append(d.Option, cdd.DuplexOption{cdd.DuplexLongEdge, def, o.optionKeyword}) + d.Option = append(d.Option, cdd.DuplexOption{cdd.DuplexLongEdge, def}) + duplexMap[cdd.DuplexLongEdge] = e.mainKeyword + internalKeySeparator + o.optionKeyword foundDefault = foundDefault || def } } } if len(d.Option) == 0 { - return nil + return nil, nil } if !foundDefault { d.Option[0].IsDefault = true } - return &d + return &d, duplexMap } func convertDPI(e entry) *cdd.DPI { diff --git a/cups/translate-ppd_test.go b/cups/translate-ppd_test.go index cdc96b2..d361fcb 100644 --- a/cups/translate-ppd_test.go +++ b/cups/translate-ppd_test.go @@ -14,13 +14,20 @@ import ( "testing" "github.com/google/cloud-print-connector/cdd" + "github.com/google/cloud-print-connector/lib" ) -func translationTest(t *testing.T, ppd string, expected *cdd.PrinterDescriptionSection) { - description, _, _ := translatePPD(ppd) - if !reflect.DeepEqual(expected, description) { +type testdata struct { + Pds *cdd.PrinterDescriptionSection + Dm lib.DuplexVendorMap +} + +func translationTest(t *testing.T, ppd string, expected testdata) { + description, _, _, dm := translatePPD(ppd) + actual := testdata{description, dm} + if !reflect.DeepEqual(expected, actual) { e, _ := json.Marshal(expected) - d, _ := json.Marshal(description) + d, _ := json.Marshal(actual) t.Logf("expected\n %s\ngot\n %s", e, d) t.Fail() } @@ -29,14 +36,17 @@ func translationTest(t *testing.T, ppd string, expected *cdd.PrinterDescriptionS func TestTrPrintingSpeed(t *testing.T) { ppd := `*PPD-Adobe: "4.3" *Throughput: "30"` - expected := &cdd.PrinterDescriptionSection{ - PrintingSpeed: &cdd.PrintingSpeed{ - []cdd.PrintingSpeedOption{ - cdd.PrintingSpeedOption{ - SpeedPPM: 30.0, + expected := testdata{ + &cdd.PrinterDescriptionSection{ + PrintingSpeed: &cdd.PrintingSpeed{ + []cdd.PrintingSpeedOption{ + cdd.PrintingSpeedOption{ + SpeedPPM: 30.0, + }, }, }, }, + nil, } translationTest(t, ppd, expected) } @@ -52,17 +62,20 @@ func TestTrMediaSize(t *testing.T) { *PageSize HalfLetter/5.5x8.5: "" *PageSize w81h252/Address - 1 1/8 x 3 1/2": "<>setpagedevice" *CloseUI: *PageSize` - expected := &cdd.PrinterDescriptionSection{ - MediaSize: &cdd.MediaSize{ - Option: []cdd.MediaSizeOption{ - cdd.MediaSizeOption{cdd.MediaSizeISOA3, mmToMicrons(297), mmToMicrons(420), false, false, "", "A3", cdd.NewLocalizedString("A3")}, - cdd.MediaSizeOption{cdd.MediaSizeISOB5, mmToMicrons(176), mmToMicrons(250), false, false, "", "ISOB5", cdd.NewLocalizedString("B5 (ISO)")}, - cdd.MediaSizeOption{cdd.MediaSizeJISB5, mmToMicrons(182), mmToMicrons(257), false, false, "", "B5", cdd.NewLocalizedString("B5 (JIS)")}, - cdd.MediaSizeOption{cdd.MediaSizeNALetter, inchesToMicrons(8.5), inchesToMicrons(11), false, true, "", "Letter", cdd.NewLocalizedString("Letter")}, - cdd.MediaSizeOption{cdd.MediaSizeCustom, inchesToMicrons(5.5), inchesToMicrons(8.5), false, false, "", "HalfLetter", cdd.NewLocalizedString("5.5x8.5")}, - cdd.MediaSizeOption{cdd.MediaSizeCustom, pointsToMicrons(81), pointsToMicrons(252), false, false, "", "w81h252", cdd.NewLocalizedString(`Address - 1 1/8 x 3 1/2"`)}, + expected := testdata{ + &cdd.PrinterDescriptionSection{ + MediaSize: &cdd.MediaSize{ + Option: []cdd.MediaSizeOption{ + cdd.MediaSizeOption{cdd.MediaSizeISOA3, mmToMicrons(297), mmToMicrons(420), false, false, "", "A3", cdd.NewLocalizedString("A3")}, + cdd.MediaSizeOption{cdd.MediaSizeISOB5, mmToMicrons(176), mmToMicrons(250), false, false, "", "ISOB5", cdd.NewLocalizedString("B5 (ISO)")}, + cdd.MediaSizeOption{cdd.MediaSizeJISB5, mmToMicrons(182), mmToMicrons(257), false, false, "", "B5", cdd.NewLocalizedString("B5 (JIS)")}, + cdd.MediaSizeOption{cdd.MediaSizeNALetter, inchesToMicrons(8.5), inchesToMicrons(11), false, true, "", "Letter", cdd.NewLocalizedString("Letter")}, + cdd.MediaSizeOption{cdd.MediaSizeCustom, inchesToMicrons(5.5), inchesToMicrons(8.5), false, false, "", "HalfLetter", cdd.NewLocalizedString("5.5x8.5")}, + cdd.MediaSizeOption{cdd.MediaSizeCustom, pointsToMicrons(81), pointsToMicrons(252), false, false, "", "w81h252", cdd.NewLocalizedString(`Address - 1 1/8 x 3 1/2"`)}, + }, }, }, + nil, } translationTest(t, ppd, expected) } @@ -74,14 +87,16 @@ func TestTrColor(t *testing.T) { *ColorModel CMYK/Color: "(cmyk) RCsetdevicecolor" *ColorModel Gray/Black and White: "(gray) RCsetdevicecolor" *CloseUI: *ColorModel` - expected := &cdd.PrinterDescriptionSection{ - Color: &cdd.Color{ - Option: []cdd.ColorOption{ - cdd.ColorOption{"CMYK", cdd.ColorTypeStandardColor, "", false, cdd.NewLocalizedString("Color")}, - cdd.ColorOption{"Gray", cdd.ColorTypeStandardMonochrome, "", true, cdd.NewLocalizedString("Black and White")}, + expected := testdata{ + &cdd.PrinterDescriptionSection{ + Color: &cdd.Color{ + Option: []cdd.ColorOption{ + cdd.ColorOption{"ColorModel:CMYK", cdd.ColorTypeStandardColor, "", false, cdd.NewLocalizedString("Color")}, + cdd.ColorOption{"ColorModel:Gray", cdd.ColorTypeStandardMonochrome, "", true, cdd.NewLocalizedString("Black and White")}, + }, }, - VendorKey: "ColorModel", }, + nil, } translationTest(t, ppd, expected) @@ -97,14 +112,16 @@ func TestTrColor(t *testing.T) { *End *CloseUI: *CMAndResolution ` - expected = &cdd.PrinterDescriptionSection{ - Color: &cdd.Color{ - Option: []cdd.ColorOption{ - cdd.ColorOption{"CMYKImageRET3600", cdd.ColorTypeStandardColor, "", true, cdd.NewLocalizedString("Color")}, - cdd.ColorOption{"Gray600x600dpi", cdd.ColorTypeStandardMonochrome, "", false, cdd.NewLocalizedString("Gray")}, + expected = testdata{ + &cdd.PrinterDescriptionSection{ + Color: &cdd.Color{ + Option: []cdd.ColorOption{ + cdd.ColorOption{"CMAndResolution:CMYKImageRET3600", cdd.ColorTypeStandardColor, "", true, cdd.NewLocalizedString("Color")}, + cdd.ColorOption{"CMAndResolution:Gray600x600dpi", cdd.ColorTypeStandardMonochrome, "", false, cdd.NewLocalizedString("Gray")}, + }, }, - VendorKey: "CMAndResolution", }, + nil, } translationTest(t, ppd, expected) @@ -117,15 +134,17 @@ func TestTrColor(t *testing.T) { *CMAndResolution Gray600x600dpi/On - 600 dpi: "<> setpagedevice" *CloseUI: *CMAndResolution ` - expected = &cdd.PrinterDescriptionSection{ - Color: &cdd.Color{ - Option: []cdd.ColorOption{ - cdd.ColorOption{"CMYKImageRET2400", cdd.ColorTypeStandardColor, "", true, cdd.NewLocalizedString("Color, ImageRET 2400")}, - cdd.ColorOption{"Gray1200x1200dpi", cdd.ColorTypeCustomMonochrome, "", false, cdd.NewLocalizedString("Gray, ProRes 1200")}, - cdd.ColorOption{"Gray600x600dpi", cdd.ColorTypeCustomMonochrome, "", false, cdd.NewLocalizedString("Gray, 600 dpi")}, + expected = testdata{ + &cdd.PrinterDescriptionSection{ + Color: &cdd.Color{ + Option: []cdd.ColorOption{ + cdd.ColorOption{"CMAndResolution:CMYKImageRET2400", cdd.ColorTypeStandardColor, "", true, cdd.NewLocalizedString("Color, ImageRET 2400")}, + cdd.ColorOption{"CMAndResolution:Gray1200x1200dpi", cdd.ColorTypeCustomMonochrome, "", false, cdd.NewLocalizedString("Gray, ProRes 1200")}, + cdd.ColorOption{"CMAndResolution:Gray600x600dpi", cdd.ColorTypeCustomMonochrome, "", false, cdd.NewLocalizedString("Gray, 600 dpi")}, + }, }, - VendorKey: "CMAndResolution", }, + nil, } translationTest(t, ppd, expected) @@ -137,14 +156,16 @@ func TestTrColor(t *testing.T) { *SelectColor Grayscale/Grayscale: "<> setpagedevice" *CloseUI: *SelectColor ` - expected = &cdd.PrinterDescriptionSection{ - Color: &cdd.Color{ - Option: []cdd.ColorOption{ - cdd.ColorOption{"Color", cdd.ColorTypeStandardColor, "", true, cdd.NewLocalizedString("Color")}, - cdd.ColorOption{"Grayscale", cdd.ColorTypeStandardMonochrome, "", false, cdd.NewLocalizedString("Grayscale")}, + expected = testdata{ + &cdd.PrinterDescriptionSection{ + Color: &cdd.Color{ + Option: []cdd.ColorOption{ + cdd.ColorOption{"SelectColor:Color", cdd.ColorTypeStandardColor, "", true, cdd.NewLocalizedString("Color")}, + cdd.ColorOption{"SelectColor:Grayscale", cdd.ColorTypeStandardMonochrome, "", false, cdd.NewLocalizedString("Grayscale")}, + }, }, - VendorKey: "SelectColor", }, + nil, } translationTest(t, ppd, expected) } @@ -156,13 +177,18 @@ func TestTrDuplex(t *testing.T) { *Duplex None/Off: "" *Duplex DuplexNoTumble/Long Edge: "" *CloseUI: *Duplex` - expected := &cdd.PrinterDescriptionSection{ - Duplex: &cdd.Duplex{ - Option: []cdd.DuplexOption{ - cdd.DuplexOption{cdd.DuplexNoDuplex, true, "None"}, - cdd.DuplexOption{cdd.DuplexLongEdge, false, "DuplexNoTumble"}, + expected := testdata{ + &cdd.PrinterDescriptionSection{ + Duplex: &cdd.Duplex{ + Option: []cdd.DuplexOption{ + cdd.DuplexOption{cdd.DuplexNoDuplex, true}, + cdd.DuplexOption{cdd.DuplexLongEdge, false}, + }, }, - VendorKey: "Duplex", + }, + lib.DuplexVendorMap{ + cdd.DuplexNoDuplex: "Duplex:None", + cdd.DuplexLongEdge: "Duplex:DuplexNoTumble", }, } translationTest(t, ppd, expected) @@ -184,14 +210,20 @@ func TestTrKMDuplex(t *testing.T) { *End *CloseUI: *KMDuplex ` - expected := &cdd.PrinterDescriptionSection{ - Duplex: &cdd.Duplex{ - Option: []cdd.DuplexOption{ - cdd.DuplexOption{cdd.DuplexNoDuplex, false, "Single"}, - cdd.DuplexOption{cdd.DuplexLongEdge, true, "Double"}, - cdd.DuplexOption{cdd.DuplexShortEdge, false, "Booklet"}, + expected := testdata{ + &cdd.PrinterDescriptionSection{ + Duplex: &cdd.Duplex{ + Option: []cdd.DuplexOption{ + cdd.DuplexOption{cdd.DuplexNoDuplex, false}, + cdd.DuplexOption{cdd.DuplexLongEdge, true}, + cdd.DuplexOption{cdd.DuplexShortEdge, false}, + }, }, - VendorKey: "KMDuplex", + }, + lib.DuplexVendorMap{ + cdd.DuplexNoDuplex: "KMDuplex:Single", + cdd.DuplexLongEdge: "KMDuplex:Double", + cdd.DuplexShortEdge: "KMDuplex:Booklet", }, } translationTest(t, ppd, expected) @@ -204,13 +236,18 @@ func TestTrKMDuplex(t *testing.T) { *KMDuplex True/On: "<< /Duplex true >> setpagedevice" *CloseUI: *KMDuplex ` - expected = &cdd.PrinterDescriptionSection{ - Duplex: &cdd.Duplex{ - Option: []cdd.DuplexOption{ - cdd.DuplexOption{cdd.DuplexNoDuplex, true, "False"}, - cdd.DuplexOption{cdd.DuplexLongEdge, false, "True"}, + expected = testdata{ + &cdd.PrinterDescriptionSection{ + Duplex: &cdd.Duplex{ + Option: []cdd.DuplexOption{ + cdd.DuplexOption{cdd.DuplexNoDuplex, true}, + cdd.DuplexOption{cdd.DuplexLongEdge, false}, + }, }, - VendorKey: "KMDuplex", + }, + lib.DuplexVendorMap{ + cdd.DuplexNoDuplex: "KMDuplex:False", + cdd.DuplexLongEdge: "KMDuplex:True", }, } translationTest(t, ppd, expected) @@ -224,14 +261,17 @@ func TestTrDPI(t *testing.T) { *Resolution 1200x600dpi/1200x600 dpi: "" *Resolution 1200x1200dpi/1200 dpi: "" *CloseUI: *Resolution` - expected := &cdd.PrinterDescriptionSection{ - DPI: &cdd.DPI{ - Option: []cdd.DPIOption{ - cdd.DPIOption{600, 600, true, "", "600dpi", cdd.NewLocalizedString("600 dpi")}, - cdd.DPIOption{1200, 600, false, "", "1200x600dpi", cdd.NewLocalizedString("1200x600 dpi")}, - cdd.DPIOption{1200, 1200, false, "", "1200x1200dpi", cdd.NewLocalizedString("1200 dpi")}, + expected := testdata{ + &cdd.PrinterDescriptionSection{ + DPI: &cdd.DPI{ + Option: []cdd.DPIOption{ + cdd.DPIOption{600, 600, true, "", "600dpi", cdd.NewLocalizedString("600 dpi")}, + cdd.DPIOption{1200, 600, false, "", "1200x600dpi", cdd.NewLocalizedString("1200x600 dpi")}, + cdd.DPIOption{1200, 1200, false, "", "1200x1200dpi", cdd.NewLocalizedString("1200 dpi")}, + }, }, }, + nil, } translationTest(t, ppd, expected) } @@ -245,21 +285,24 @@ func TestTrInputSlot(t *testing.T) { *OutputBin Bin1/Internal Tray 2: "" *OutputBin External/External Tray: "" *CloseUI: *OutputBin` - expected := &cdd.PrinterDescriptionSection{ - VendorCapability: &[]cdd.VendorCapability{ - cdd.VendorCapability{ - ID: "OutputBin", - Type: cdd.VendorCapabilitySelect, - DisplayNameLocalized: cdd.NewLocalizedString("Destination"), - SelectCap: &cdd.SelectCapability{ - Option: []cdd.SelectCapabilityOption{ - cdd.SelectCapabilityOption{"Standard", "", true, cdd.NewLocalizedString("Internal Tray 1")}, - cdd.SelectCapabilityOption{"Bin1", "", false, cdd.NewLocalizedString("Internal Tray 2")}, - cdd.SelectCapabilityOption{"External", "", false, cdd.NewLocalizedString("External Tray")}, + expected := testdata{ + &cdd.PrinterDescriptionSection{ + VendorCapability: &[]cdd.VendorCapability{ + cdd.VendorCapability{ + ID: "OutputBin", + Type: cdd.VendorCapabilitySelect, + DisplayNameLocalized: cdd.NewLocalizedString("Destination"), + SelectCap: &cdd.SelectCapability{ + Option: []cdd.SelectCapabilityOption{ + cdd.SelectCapabilityOption{"Standard", "", true, cdd.NewLocalizedString("Internal Tray 1")}, + cdd.SelectCapabilityOption{"Bin1", "", false, cdd.NewLocalizedString("Internal Tray 2")}, + cdd.SelectCapabilityOption{"External", "", false, cdd.NewLocalizedString("External Tray")}, + }, }, }, }, }, + nil, } translationTest(t, ppd, expected) } @@ -272,21 +315,24 @@ func TestTrPrintQuality(t *testing.T) { *HPPrintQuality 600dpi/600 dpi: "" *HPPrintQuality ProRes1200/ProRes 1200: "" *CloseUI: *HPPrintQuality` - expected := &cdd.PrinterDescriptionSection{ - VendorCapability: &[]cdd.VendorCapability{ - cdd.VendorCapability{ - ID: "HPPrintQuality", - Type: cdd.VendorCapabilitySelect, - DisplayNameLocalized: cdd.NewLocalizedString("Print Quality"), - SelectCap: &cdd.SelectCapability{ - Option: []cdd.SelectCapabilityOption{ - cdd.SelectCapabilityOption{"FastRes1200", "", true, cdd.NewLocalizedString("FastRes 1200")}, - cdd.SelectCapabilityOption{"600dpi", "", false, cdd.NewLocalizedString("600 dpi")}, - cdd.SelectCapabilityOption{"ProRes1200", "", false, cdd.NewLocalizedString("ProRes 1200")}, + expected := testdata{ + &cdd.PrinterDescriptionSection{ + VendorCapability: &[]cdd.VendorCapability{ + cdd.VendorCapability{ + ID: "HPPrintQuality", + Type: cdd.VendorCapabilitySelect, + DisplayNameLocalized: cdd.NewLocalizedString("Print Quality"), + SelectCap: &cdd.SelectCapability{ + Option: []cdd.SelectCapabilityOption{ + cdd.SelectCapabilityOption{"FastRes1200", "", true, cdd.NewLocalizedString("FastRes 1200")}, + cdd.SelectCapabilityOption{"600dpi", "", false, cdd.NewLocalizedString("600 dpi")}, + cdd.SelectCapabilityOption{"ProRes1200", "", false, cdd.NewLocalizedString("ProRes 1200")}, + }, }, }, }, }, + nil, } translationTest(t, ppd, expected) } @@ -318,17 +364,20 @@ func TestRicohLockedPrint(t *testing.T) { *CustomLockedPrintPassword True/Custom Password: "" *ParamCustomLockedPrintPassword Password: 1 passcode 4 8 ` - expected := &cdd.PrinterDescriptionSection{ - VendorCapability: &[]cdd.VendorCapability{ - cdd.VendorCapability{ - ID: "JobType:LockedPrint/LockedPrintPassword", - Type: cdd.VendorCapabilityTypedValue, - DisplayNameLocalized: cdd.NewLocalizedString("Password (4 numbers)"), - TypedValueCap: &cdd.TypedValueCapability{ - ValueType: cdd.TypedValueCapabilityTypeString, + expected := testdata{ + &cdd.PrinterDescriptionSection{ + VendorCapability: &[]cdd.VendorCapability{ + cdd.VendorCapability{ + ID: "JobType:LockedPrint/LockedPrintPassword", + Type: cdd.VendorCapabilityTypedValue, + DisplayNameLocalized: cdd.NewLocalizedString("Password (4 numbers)"), + TypedValueCap: &cdd.TypedValueCapability{ + ValueType: cdd.TypedValueCapabilityTypeString, + }, }, }, }, + nil, } translationTest(t, ppd, expected) } diff --git a/cups/translate-ticket.go b/cups/translate-ticket.go index 861907e..2e96b3b 100644 --- a/cups/translate-ticket.go +++ b/cups/translate-ticket.go @@ -48,22 +48,28 @@ func translateTicket(printer *lib.Printer, ticket *cdd.CloudJobTicket) (map[stri } } if ticket.Print.Color != nil && printer.Description.Color != nil { + var colorString string if ticket.Print.Color.VendorID != "" { - m[printer.Description.Color.VendorKey] = ticket.Print.Color.VendorID + colorString = ticket.Print.Color.VendorID } else { - // The ticket doesn't provide the VendorID. Let's find it. + // The ticket doesn't provide the VendorID. Let's find it by Type. for _, colorOption := range printer.Description.Color.Option { if ticket.Print.Color.Type == colorOption.Type { - m[printer.Description.Color.VendorKey] = colorOption.VendorID + colorString = colorOption.VendorID + break } } } + parts := rVendorIDKeyValue.FindStringSubmatch(colorString) + if parts != nil && parts[2] != "" { + m[parts[1]] = parts[2] + } } if ticket.Print.Duplex != nil && printer.Description.Duplex != nil { - for _, duplexOption := range printer.Description.Duplex.Option { - if ticket.Print.Duplex.Type == duplexOption.Type { - m[printer.Description.Duplex.VendorKey] = duplexOption.VendorID - } + duplexString := printer.DuplexMap[ticket.Print.Duplex.Type] + parts := rVendorIDKeyValue.FindStringSubmatch(duplexString) + if parts != nil && parts[2] != "" { + m[parts[1]] = parts[2] } } if ticket.Print.PageOrientation != nil && printer.Description.PageOrientation != nil { diff --git a/cups/translate-ticket_test.go b/cups/translate-ticket_test.go index 9053278..aeac954 100644 --- a/cups/translate-ticket_test.go +++ b/cups/translate-ticket_test.go @@ -48,20 +48,17 @@ func TestTranslateTicket(t *testing.T) { Color: &cdd.Color{ Option: []cdd.ColorOption{ cdd.ColorOption{ - VendorID: "zebra-stripes", + VendorID: "ColorModel:zebra-stripes", Type: cdd.ColorTypeCustomMonochrome, }, }, - VendorKey: "ColorModel", }, Duplex: &cdd.Duplex{ Option: []cdd.DuplexOption{ cdd.DuplexOption{ - Type: cdd.DuplexNoDuplex, - VendorID: "None", + Type: cdd.DuplexNoDuplex, }, }, - VendorKey: "Duplex", }, PageOrientation: &cdd.PageOrientation{}, Copies: &cdd.Copies{}, @@ -80,13 +77,16 @@ func TestTranslateTicket(t *testing.T) { Collate: &cdd.Collate{}, ReverseOrder: &cdd.ReverseOrder{}, }, + DuplexMap: lib.DuplexVendorMap{ + cdd.DuplexNoDuplex: "Duplex:None", + }, } ticket.Print = cdd.PrintTicketSection{ VendorTicketItem: []cdd.VendorTicketItem{ cdd.VendorTicketItem{"number-up", "a"}, cdd.VendorTicketItem{"a:b/c:d/e", "f"}, }, - Color: &cdd.ColorTicketItem{VendorID: "zebra-stripes", Type: cdd.ColorTypeCustomMonochrome}, + Color: &cdd.ColorTicketItem{VendorID: "ColorModel:zebra-stripes", Type: cdd.ColorTypeCustomMonochrome}, Duplex: &cdd.DuplexTicketItem{Type: cdd.DuplexNoDuplex}, PageOrientation: &cdd.PageOrientationTicketItem{Type: cdd.PageOrientationAuto}, Copies: &cdd.CopiesTicketItem{Copies: 2}, @@ -139,20 +139,17 @@ func TestTranslateTicket(t *testing.T) { Color: &cdd.Color{ Option: []cdd.ColorOption{ cdd.ColorOption{ - VendorID: "color", + VendorID: "print-color-mode:color", Type: cdd.ColorTypeStandardColor, }, }, - VendorKey: "print-color-mode", }, Duplex: &cdd.Duplex{ Option: []cdd.DuplexOption{ cdd.DuplexOption{ - Type: cdd.DuplexLongEdge, - VendorID: "Single", + Type: cdd.DuplexLongEdge, }, }, - VendorKey: "KMDuplex", }, PageOrientation: &cdd.PageOrientation{}, DPI: &cdd.DPI{ @@ -166,8 +163,11 @@ func TestTranslateTicket(t *testing.T) { }, MediaSize: &cdd.MediaSize{}, } + printer.DuplexMap = lib.DuplexVendorMap{ + cdd.DuplexLongEdge: "KMDuplex:Single", + } ticket.Print = cdd.PrintTicketSection{ - Color: &cdd.ColorTicketItem{VendorID: "color", Type: cdd.ColorTypeStandardColor}, + Color: &cdd.ColorTicketItem{VendorID: "print-color-mode:color", Type: cdd.ColorTypeStandardColor}, Duplex: &cdd.DuplexTicketItem{Type: cdd.DuplexLongEdge}, PageOrientation: &cdd.PageOrientationTicketItem{Type: cdd.PageOrientationLandscape}, DPI: &cdd.DPITicketItem{100, 100, ""}, @@ -193,14 +193,13 @@ func TestTranslateTicket(t *testing.T) { printer.Description.Color = &cdd.Color{ Option: []cdd.ColorOption{ cdd.ColorOption{ - VendorID: "Gray600x600dpi", + VendorID: "CMAndResolution:Gray600x600dpi", Type: cdd.ColorTypeStandardColor, }, }, - VendorKey: "CMAndResolution", } ticket.Print = cdd.PrintTicketSection{ - Color: &cdd.ColorTicketItem{VendorID: "Gray600x600dpi", Type: cdd.ColorTypeStandardColor}, + Color: &cdd.ColorTicketItem{VendorID: "CMAndResolution:Gray600x600dpi", Type: cdd.ColorTypeStandardColor}, } expected = map[string]string{ "CMAndResolution": "Gray600x600dpi", @@ -218,14 +217,13 @@ func TestTranslateTicket(t *testing.T) { printer.Description.Color = &cdd.Color{ Option: []cdd.ColorOption{ cdd.ColorOption{ - VendorID: "Color", + VendorID: "SelectColor:Color", Type: cdd.ColorTypeStandardColor, }, }, - VendorKey: "SelectColor", } ticket.Print = cdd.PrintTicketSection{ - Color: &cdd.ColorTicketItem{VendorID: "Color"}, + Color: &cdd.ColorTicketItem{VendorID: "SelectColor:Color"}, } expected = map[string]string{ "SelectColor": "Color", diff --git a/lib/printer.go b/lib/printer.go index 2a1df0c..d56f62d 100644 --- a/lib/printer.go +++ b/lib/printer.go @@ -9,8 +9,6 @@ https://developers.google.com/open-source/licenses/bsd package lib import ( - "bytes" - "encoding/json" "reflect" "regexp" @@ -19,6 +17,9 @@ import ( type PrinterState uint8 +// DuplexVendorMap maps a DuplexType to a CUPS key:value option string for a given printer. +type DuplexVendorMap map[cdd.DuplexType]string + // CUPS: cups_dest_t; GCP: /register and /update interfaces type Printer struct { GCPID string // GCP: printerid (GCP key) @@ -36,6 +37,7 @@ type Printer struct { Description *cdd.PrinterDescriptionSection // CUPS: translated PPD; GCP: capabilities field CapsHash string // CUPS: hash of PPD; GCP: capsHash field Tags map[string]string // CUPS: all printer attributes; GCP: repeated tag field + DuplexMap DuplexVendorMap // CUPS: PPD; NativeJobSemaphore *Semaphore QuotaEnabled bool DailyQuota int @@ -85,6 +87,7 @@ type PrinterDiff struct { DescriptionChanged bool CapsHashChanged bool TagsChanged bool + DuplexMapChanged bool QuotaEnabledChanged bool DailyQuotaChanged bool } @@ -191,11 +194,7 @@ func diffPrinter(pn, pg *Printer) PrinterDiff { if !reflect.DeepEqual(pg.State, pn.State) { d.StateChanged = true } - // PrinterDescriptionSection objects contain fields that are not exported to JSON, - // and therefore cause comparison with GCP's copy to incorrectly appear not equal. - pgDescJSON, _ := json.Marshal(pg.Description) - pnDescJSON, _ := json.Marshal(pn.Description) - if !bytes.Equal(pgDescJSON, pnDescJSON) { + if !reflect.DeepEqual(pg.Description, pn.Description) { d.DescriptionChanged = true } if pg.CapsHash != pn.CapsHash { @@ -208,6 +207,10 @@ func diffPrinter(pn, pg *Printer) PrinterDiff { d.TagsChanged = true } + if !reflect.DeepEqual(pg.DuplexMap, pn.DuplexMap) { + d.DuplexMapChanged = true + } + if pg.QuotaEnabled != pn.QuotaEnabled { d.QuotaEnabledChanged = true } @@ -220,7 +223,7 @@ func diffPrinter(pn, pg *Printer) PrinterDiff { d.GCPVersionChanged || d.SetupURLChanged || d.SupportURLChanged || d.UpdateURLChanged || d.ConnectorVersionChanged || d.StateChanged || d.DescriptionChanged || d.CapsHashChanged || d.TagsChanged || - d.QuotaEnabledChanged || d.DailyQuotaChanged { + d.DuplexMapChanged || d.QuotaEnabledChanged || d.DailyQuotaChanged { return d } From e797578f942f968c479e0d9c26b7c0aad5d808c2 Mon Sep 17 00:00:00 2001 From: Nick Westgate Date: Thu, 7 Jul 2016 05:41:37 +1200 Subject: [PATCH 24/76] Add printer whitelist (#263) * Add printer whitelist - Based on existing blacklist code regions. - If the whitelist is empty then it is disabled. - The blacklist overrides the whitelist. The filterBlacklistPrinters function was duplicated in cups.go and winspool.go, so instead of creating two more almost identical filterWhitelistPrinters functions I factored out the common code in lib/printer.go. * Tests for printer.go So far only testing the newly added functions: - FilterBlacklistPrinters - FilterWhitelistPrinters * Moved FilterRawPrinters Because the code I previously inserted visually separated it from its dependent function PrinterIsRaw. --- cups/cups.go | 23 ++++--- gcp-connector-util/main_unix.go | 2 + gcp-connector-util/main_windows.go | 2 + gcp-cups-connector/gcp-cups-connector.go | 2 +- .../gcp-windows-connector.go | 2 +- lib/config.go | 3 + lib/config_unix.go | 4 ++ lib/config_windows.go | 4 ++ lib/printer.go | 22 +++++++ lib/printer_test.go | 60 +++++++++++++++++++ winspool/winspool.go | 22 ++++--- 11 files changed, 120 insertions(+), 26 deletions(-) create mode 100644 lib/printer_test.go diff --git a/cups/cups.go b/cups/cups.go index e99493f..affb48a 100644 --- a/cups/cups.go +++ b/cups/cups.go @@ -136,11 +136,12 @@ type CUPS struct { printerAttributes []string systemTags map[string]string printerBlacklist map[string]interface{} + printerWhitelist map[string]interface{} ignoreRawPrinters bool ignoreClassPrinters bool } -func NewCUPS(infoToDisplayName, prefixJobIDToJobTitle bool, displayNamePrefix string, printerAttributes []string, maxConnections uint, connectTimeout time.Duration, printerBlacklist []string, ignoreRawPrinters bool, ignoreClassPrinters bool) (*CUPS, error) { +func NewCUPS(infoToDisplayName, prefixJobIDToJobTitle bool, displayNamePrefix string, printerAttributes []string, maxConnections uint, connectTimeout time.Duration, printerBlacklist []string, printerWhitelist []string, ignoreRawPrinters bool, ignoreClassPrinters bool) (*CUPS, error) { if err := checkPrinterAttributes(printerAttributes); err != nil { return nil, err } @@ -161,6 +162,11 @@ func NewCUPS(infoToDisplayName, prefixJobIDToJobTitle bool, displayNamePrefix st pb[p] = struct{}{} } + pw := map[string]interface{}{} + for _, p := range printerWhitelist { + pw[p] = struct{}{} + } + c := &CUPS{ cc: cc, pc: pc, @@ -169,6 +175,7 @@ func NewCUPS(infoToDisplayName, prefixJobIDToJobTitle bool, displayNamePrefix st printerAttributes: printerAttributes, systemTags: systemTags, printerBlacklist: pb, + printerWhitelist: pw, ignoreRawPrinters: ignoreRawPrinters, ignoreClassPrinters: ignoreClassPrinters, } @@ -212,7 +219,9 @@ func (c *CUPS) GetPrinters() ([]lib.Printer, error) { } printers := c.responseToPrinters(response) - printers = c.filterBlacklistPrinters(printers) + printers = lib.FilterBlacklistPrinters(printers, c.printerBlacklist) + printers = lib.FilterWhitelistPrinters(printers, c.printerWhitelist) + if c.ignoreRawPrinters { printers = filterRawPrinters(printers) } @@ -263,16 +272,6 @@ func (c *CUPS) responseToPrinters(response *C.ipp_t) []lib.Printer { return printers } -func (c *CUPS) filterBlacklistPrinters(printers []lib.Printer) []lib.Printer { - result := make([]lib.Printer, 0, len(printers)) - for i := range printers { - if _, exists := c.printerBlacklist[printers[i].Name]; !exists { - result = append(result, printers[i]) - } - } - return result -} - // filterClassPrinters removes class printers from the slice. func filterClassPrinters(printers []lib.Printer) []lib.Printer { result := make([]lib.Printer, 0, len(printers)) diff --git a/gcp-connector-util/main_unix.go b/gcp-connector-util/main_unix.go index 0cd74a1..8efefda 100644 --- a/gcp-connector-util/main_unix.go +++ b/gcp-connector-util/main_unix.go @@ -136,6 +136,7 @@ func createCloudConfig(context *cli.Context, xmppJID, robotRefreshToken, userRef PrefixJobIDToJobTitle: lib.PointerToBool(context.Bool("prefix-job-id-to-job-title")), DisplayNamePrefix: context.String("display-name-prefix"), PrinterBlacklist: lib.DefaultConfig.PrinterBlacklist, + PrinterWhitelist: lib.DefaultConfig.PrinterWhitelist, LogLevel: context.String("log-level"), LocalPortLow: uint16(context.Int("local-port-low")), @@ -167,6 +168,7 @@ func createLocalConfig(context *cli.Context) *lib.Config { PrefixJobIDToJobTitle: lib.PointerToBool(context.Bool("prefix-job-id-to-job-title")), DisplayNamePrefix: context.String("display-name-prefix"), PrinterBlacklist: lib.DefaultConfig.PrinterBlacklist, + PrinterWhitelist: lib.DefaultConfig.PrinterWhitelist, LogLevel: context.String("log-level"), LocalPortLow: uint16(context.Int("local-port-low")), diff --git a/gcp-connector-util/main_windows.go b/gcp-connector-util/main_windows.go index 2a979e6..497c8d8 100644 --- a/gcp-connector-util/main_windows.go +++ b/gcp-connector-util/main_windows.go @@ -225,6 +225,7 @@ func createCloudConfig(context *cli.Context, xmppJID, robotRefreshToken, userRef PrefixJobIDToJobTitle: lib.PointerToBool(context.Bool("prefix-job-id-to-job-title")), DisplayNamePrefix: context.String("display-name-prefix"), PrinterBlacklist: lib.DefaultConfig.PrinterBlacklist, + PrinterWhitelist: lib.DefaultConfig.PrinterWhitelist, LogLevel: context.String("log-level"), LocalPortLow: uint16(context.Int("local-port-low")), @@ -244,6 +245,7 @@ func createLocalConfig(context *cli.Context) *lib.Config { PrefixJobIDToJobTitle: lib.PointerToBool(context.Bool("prefix-job-id-to-job-title")), DisplayNamePrefix: context.String("display-name-prefix"), PrinterBlacklist: lib.DefaultConfig.PrinterBlacklist, + PrinterWhitelist: lib.DefaultConfig.PrinterWhitelist, LogLevel: context.String("log-level"), LocalPortLow: uint16(context.Int("local-port-low")), diff --git a/gcp-cups-connector/gcp-cups-connector.go b/gcp-cups-connector/gcp-cups-connector.go index d6a2d19..f1105c3 100644 --- a/gcp-cups-connector/gcp-cups-connector.go +++ b/gcp-cups-connector/gcp-cups-connector.go @@ -156,7 +156,7 @@ func connector(context *cli.Context) int { } c, err := cups.NewCUPS(*config.CUPSCopyPrinterInfoToDisplayName, *config.PrefixJobIDToJobTitle, config.DisplayNamePrefix, config.CUPSPrinterAttributes, config.CUPSMaxConnections, - cupsConnectTimeout, config.PrinterBlacklist, *config.CUPSIgnoreRawPrinters, + cupsConnectTimeout, config.PrinterBlacklist, config.PrinterWhitelist, *config.CUPSIgnoreRawPrinters, *config.CUPSIgnoreClassPrinters) if err != nil { log.Fatal(err) diff --git a/gcp-windows-connector/gcp-windows-connector.go b/gcp-windows-connector/gcp-windows-connector.go index 7a488ae..1eb3153 100644 --- a/gcp-windows-connector/gcp-windows-connector.go +++ b/gcp-windows-connector/gcp-windows-connector.go @@ -149,7 +149,7 @@ func (service *service) Execute(args []string, r <-chan svc.ChangeRequest, s cha defer x.Quit() } - ws, err := winspool.NewWinSpool(*config.PrefixJobIDToJobTitle, config.DisplayNamePrefix, config.PrinterBlacklist) + ws, err := winspool.NewWinSpool(*config.PrefixJobIDToJobTitle, config.DisplayNamePrefix, config.PrinterBlacklist, config.PrinterWhitelist) if err != nil { log.Fatal(err) return false, 1 diff --git a/lib/config.go b/lib/config.go index 09f4923..6d42bb8 100644 --- a/lib/config.go +++ b/lib/config.go @@ -211,6 +211,9 @@ func (c *Config) commonBackfill(configMap map[string]interface{}) *Config { if _, exists := configMap["printer_blacklist"]; !exists { b.PrinterBlacklist = DefaultConfig.PrinterBlacklist } + if _, exists := configMap["printer_whitelist"]; !exists { + b.PrinterWhitelist = DefaultConfig.PrinterWhitelist + } if _, exists := configMap["local_printing_enable"]; !exists { b.LocalPrintingEnable = DefaultConfig.LocalPrintingEnable } diff --git a/lib/config_unix.go b/lib/config_unix.go index 177b7a3..d89a2d5 100644 --- a/lib/config_unix.go +++ b/lib/config_unix.go @@ -99,6 +99,9 @@ type Config struct { // Ignore printers with native names. PrinterBlacklist []string `json:"printer_blacklist,omitempty"` + // Allow printers with native names. + PrinterWhitelist []string `json:"printer_whitelist,omitempty"` + // Least severity to log. LogLevel string `json:"log_level"` @@ -166,6 +169,7 @@ var DefaultConfig = Config{ PrefixJobIDToJobTitle: PointerToBool(false), DisplayNamePrefix: "", PrinterBlacklist: []string{}, + PrinterWhitelist: []string{}, LogLevel: "INFO", LocalPortLow: 26000, diff --git a/lib/config_windows.go b/lib/config_windows.go index ffbaa02..e640e9b 100644 --- a/lib/config_windows.go +++ b/lib/config_windows.go @@ -97,6 +97,9 @@ type Config struct { // Ignore printers with native names. PrinterBlacklist []string `json:"printer_blacklist,omitempty"` + // Allow printers with native names. + PrinterWhitelist []string `json:"printer_whitelist,omitempty"` + // Least severity to log. LogLevel string `json:"log_level"` @@ -133,6 +136,7 @@ var DefaultConfig = Config{ "Microsoft XPS Document Writer", "Google Cloud Printer", }, + PrinterWhitelist: []string{}, LocalPrintingEnable: true, CloudPrintingEnable: false, LogLevel: "INFO", diff --git a/lib/printer.go b/lib/printer.go index d56f62d..3386ac9 100644 --- a/lib/printer.go +++ b/lib/printer.go @@ -233,6 +233,28 @@ func diffPrinter(pn, pg *Printer) PrinterDiff { } } +func FilterBlacklistPrinters(printers []Printer, list map[string]interface{}) []Printer { + return filterPrinters(printers, list, false) +} + +func FilterWhitelistPrinters(printers []Printer, list map[string]interface{}) []Printer { + if len(list) == 0 { + return printers; // Empty whitelist means don't use whitelist + } + + return filterPrinters(printers, list, true) +} + +func filterPrinters(printers []Printer, list map[string]interface{}, isWhitelist bool) []Printer { + result := make([]Printer, 0, len(printers)) + for i := range printers { + if _, exists := list[printers[i].Name]; exists == isWhitelist { + result = append(result, printers[i]) + } + } + return result +} + // FilterRawPrinters splits a slice of printers into non-raw and raw. func FilterRawPrinters(printers []Printer) ([]Printer, []Printer) { notRaw, raw := make([]Printer, 0, len(printers)), make([]Printer, 0, 0) diff --git a/lib/printer_test.go b/lib/printer_test.go new file mode 100644 index 0000000..44ac990 --- /dev/null +++ b/lib/printer_test.go @@ -0,0 +1,60 @@ +/* +Copyright 2016 Google Inc. All rights reserved. + +Use of this source code is governed by a BSD-style +license that can be found in the LICENSE file or at +https://developers.google.com/open-source/licenses/bsd +*/ + +package lib + +import ( + "testing" + "reflect" +) + +func TestFilterBlacklistPrinters(t *testing.T) { + printers := []Printer { + {Name: "Stay1"}, + {Name: "Go1"}, + {Name: "Go2"}, + {Name: "Stay2"}, + } + blacklist := map[string]interface{} { + "Go1": "", + "Go2": "", + } + correctFilteredPrinters := []Printer { + {Name: "Stay1"}, + {Name: "Stay2"}, + } + + filteredPrinters := FilterBlacklistPrinters(printers, blacklist) + + if !reflect.DeepEqual(filteredPrinters, correctFilteredPrinters) { + t.Fatalf("filtering result incorrect: %v", filteredPrinters) + } +} + +func TestFilterWhitelistPrinters(t *testing.T) { + printers := []Printer { + {Name: "Stay1"}, + {Name: "Go1"}, + {Name: "Go2"}, + {Name: "Stay2"}, + } + whitelist := map[string]interface{} { + "Stay1": "", + "Stay2": "", + } + correctFilteredPrinters := []Printer { + {Name: "Stay1"}, + {Name: "Stay2"}, + } + + filteredPrinters := FilterWhitelistPrinters(printers, whitelist) + + if !reflect.DeepEqual(filteredPrinters, correctFilteredPrinters) { + t.Fatalf("filtering result incorrect: %v", filteredPrinters) + } +} diff --git a/winspool/winspool.go b/winspool/winspool.go index 3fbba08..b00b129 100644 --- a/winspool/winspool.go +++ b/winspool/winspool.go @@ -45,9 +45,10 @@ type WinSpool struct { displayNamePrefix string systemTags map[string]string printerBlacklist map[string]interface{} + printerWhitelist map[string]interface{} } -func NewWinSpool(prefixJobIDToJobTitle bool, displayNamePrefix string, printerBlacklist []string) (*WinSpool, error) { +func NewWinSpool(prefixJobIDToJobTitle bool, displayNamePrefix string, printerBlacklist []string, printerWhitelist []string) (*WinSpool, error) { systemTags, err := getSystemTags() if err != nil { return nil, err @@ -58,11 +59,17 @@ func NewWinSpool(prefixJobIDToJobTitle bool, displayNamePrefix string, printerBl pb[p] = struct{}{} } + pw := map[string]interface{}{} + for _, p := range printerWhitelist { + pw[p] = struct{}{} + } + ws := WinSpool{ prefixJobIDToJobTitle: prefixJobIDToJobTitle, displayNamePrefix: displayNamePrefix, systemTags: systemTags, printerBlacklist: pb, + printerWhitelist: pw, } return &ws, nil } @@ -431,23 +438,14 @@ func (ws *WinSpool) GetPrinters() ([]lib.Printer, error) { printers = append(printers, printer) } - printers = ws.filterBlacklistPrinters(printers) + printers = lib.FilterBlacklistPrinters(printers, ws.printerBlacklist) + printers = lib.FilterWhitelistPrinters(printers, ws.printerWhitelist) printers = addStaticDescriptionToPrinters(printers) printers = ws.addSystemTagsToPrinters(printers) return printers, nil } -func (ws *WinSpool) filterBlacklistPrinters(printers []lib.Printer) []lib.Printer { - result := make([]lib.Printer, 0, len(printers)) - for i := range printers { - if _, exists := ws.printerBlacklist[printers[i].Name]; !exists { - result = append(result, printers[i]) - } - } - return result -} - // addStaticDescriptionToPrinters adds information that is true for all // printers to printers. func addStaticDescriptionToPrinters(printers []lib.Printer) []lib.Printer { From f84bb043db41f178beb1a2c949d11a980f40afc4 Mon Sep 17 00:00:00 2001 From: arastooz Date: Wed, 27 Jul 2016 15:39:18 -0700 Subject: [PATCH 25/76] Windows installer improvements. (#276) Move manual init cmd to before ExecuteAction in InstallUISequence to make it work properly. Move dependencies out to separate file and add script to update dependencies. Allow for setting config file path during install instead of running init --- wix/README.md | 42 +++++-- wix/dependencies.wxs | 151 ++++++++++++++++++++++++ wix/generate-dependencies.sh | 16 +++ wix/windows-connector.wxs | 223 +++++++++++++---------------------- 4 files changed, 280 insertions(+), 152 deletions(-) create mode 100644 wix/dependencies.wxs create mode 100644 wix/generate-dependencies.sh diff --git a/wix/README.md b/wix/README.md index e9a073b..1340c0b 100644 --- a/wix/README.md +++ b/wix/README.md @@ -1,18 +1,46 @@ # Windows Installer ## Build Requirements -The WIX toolset is required to build the Windows Installer file, the WIX toolset is required. +The WIX toolset is required to build the Windows Installer file. It can be downloaded from http://wixtoolset.org. ## Build Instructions +Build the Cloud Print Connector binaries. See https://github.com/google/cloud-print-connector/wiki/Build-from-source + +Update the dependencies.wxs file by running ./generate-dependencies.sh (in mingw64 bash shell). + +Use the WIX tools to build the MSI. The WIX tools that are used are candle.exe +and light.exe. They are installed by default to +"C:\Program Files (x86)\WiX Toolset v3.10\bin" +(/c/Program\ Files\ (x86)/WiX\ Toolset\ v3.10/bin/light.exe if you're using +mingw bash shell). You can add this directory to your PATH to run the following +two commands. + Run candle.exe to build wixobj file from the wxs file: -candle.exe -arch x64 windows-connector.wxs +``` +candle.exe -arch x64 windows-connector.wxs dependencies.wxs +``` + +Expected output: +> Windows Installer XML Toolset Compiler version 3.10.2.2516 +> Copyright (c) Outercurve Foundation. All rights reserved. +> +> windows-connector.wxs +> dependencies.wxs + Run light.exe to build MSI file from the wixobj -light.exe -ext "C:\Program Files (x86)\WiX Toolset v3.10\bin\WixUIExtension.dll" windows-connector.wixobj +``` +light.exe -ext "C:\Program Files (x86)\WiX Toolset v3.10\bin\WixUIExtension.dll" windows-connector.wixobj dependencies.wixobj -o windows-connector.msi +``` + +Expected output: +> Windows Installer XML Toolset Linker version 3.10.2.2516 +> Copyright (c) Outercurve Foundation. All rights reserved. -If the WIX toolset is installed to a different directory, use that directory path for the -UI extension dll. +The light.exe command line requires the path of WixUIExtension.dll which +provides the UI that is used by this installer. If the WIX toolset is installed +to a different directory, use that directory path for the UI extension dll. If the built Windows Connector binaries are not in $GOPATH\bin, then add -dSourceDir= to the light.exe command line to specify where the files can be found. @@ -28,6 +56,4 @@ will open a console window to initialize the connector. The following public properties may be set during install of the MSI (see https://msdn.microsoft.com/en-us/library/windows/desktop/aa370912(v=vs.85).aspx) -* INITCMD = Command line to use to run gcp-connector-util.exe as a silent init during the install -* NO_INSTALL_SERVICE = Set to "yes" to skip installing the service during the install -* NO_START_SERVICE = Set to "yes" to skip starting the service during the install +* CONFIGFILE = Path of connector config file to use instead of running gcp-connector-util init during install diff --git a/wix/dependencies.wxs b/wix/dependencies.wxs new file mode 100644 index 0000000..1829225 --- /dev/null +++ b/wix/dependencies.wxs @@ -0,0 +1,151 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/wix/generate-dependencies.sh b/wix/generate-dependencies.sh new file mode 100644 index 0000000..1939ebc --- /dev/null +++ b/wix/generate-dependencies.sh @@ -0,0 +1,16 @@ +#!/usr/bin/bash +echo ''>dependencies.wxs +echo ' + + '>>dependencies.wxs +for f in `ldd ${GOPATH}/bin/gcp-windows-connector.exe | grep -v Windows | sed s/" =>.*"// | sed s/"\t"//` + do echo " + + ">>dependencies.wxs; done +echo ' + +'>>dependencies.wxs + diff --git a/wix/windows-connector.wxs b/wix/windows-connector.wxs index 4614c60..65f732f 100644 --- a/wix/windows-connector.wxs +++ b/wix/windows-connector.wxs @@ -7,9 +7,11 @@ Version="1.0.0.0" Manufacturer="Google, Inc." UpgradeCode="15C3FD61-B03C-4C04-A56D-CD8424C99D7F"> - + - + @@ -19,13 +21,19 @@ - + - + @@ -38,162 +46,89 @@ + - - + + - - + + + + + + + + + + + + + NOT CONFIGFILE + + + CONFIGFILE + - NOT INITCMD + + + + NOT CONFIGFILE and NOT Installed + - - - - INITCMD - NOT NO_INSTALL_SERVICE AND NOT Installed - $Cloud_Print_Connector_exe=3 AND NOT (NO_INSTALL_SERVICE OR NO_START_SERVICE) - ?Cloud_Print_Connector_exe=3 - $Cloud_Print_Connector_exe=2 - - - - - - - - - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + From 05bbb77359bb68a898a31bd4fed3d83b76cb424e Mon Sep 17 00:00:00 2001 From: Jacob Marble Date: Fri, 29 Jul 2016 11:28:13 -0700 Subject: [PATCH 26/76] windows: Do not prompt to enable local/cloud in init tool. (#281) --- gcp-connector-util/init.go | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/gcp-connector-util/init.go b/gcp-connector-util/init.go index be5798b..8bd7816 100644 --- a/gcp-connector-util/init.go +++ b/gcp-connector-util/init.go @@ -16,6 +16,7 @@ import ( "net/url" "os" "path/filepath" + "runtime" "strings" "time" @@ -353,7 +354,10 @@ func stringToBool(val string) (bool, bool) { func initConfigFile(context *cli.Context) { var localEnable bool - if context.IsSet("local-printing-enable") { + if runtime.GOOS == "windows" { + // Remove this if block when Privet support is added to Windows. + localEnable = false + } else if context.IsSet("local-printing-enable") { localEnable = context.Bool("local-printing-enable") } else { fmt.Println("\"Local printing\" means that clients print directly to the connector via local subnet,") @@ -362,7 +366,10 @@ func initConfigFile(context *cli.Context) { } var cloudEnable bool - if context.IsSet("cloud-printing-enable") { + if runtime.GOOS == "windows" { + // Remove this if block when Privet support is added to Windows. + cloudEnable = true + } else if context.IsSet("cloud-printing-enable") { cloudEnable = context.Bool("cloud-printing-enable") } else { fmt.Println("\"Cloud printing\" means that clients can print from anywhere on the Internet,") From 3b9ffa1bdf772aead5f59ca3e54329b8c1e1d3ad Mon Sep 17 00:00:00 2001 From: Jacob Marble Date: Fri, 29 Jul 2016 16:32:57 -0700 Subject: [PATCH 27/76] CUPS: Allow any PPD option as CDD vendor capability. (#282) Fixes #195 --- cups/cups.go | 6 +- cups/ppdcache.go | 22 ++++---- cups/translate-ppd.go | 34 +++++++++++- cups/translate-ppd_test.go | 71 +++++++++++++++++++----- gcp-cups-connector/gcp-cups-connector.go | 2 +- lib/config_unix.go | 3 + 6 files changed, 109 insertions(+), 29 deletions(-) diff --git a/cups/cups.go b/cups/cups.go index affb48a..98a4ef4 100644 --- a/cups/cups.go +++ b/cups/cups.go @@ -141,7 +141,9 @@ type CUPS struct { ignoreClassPrinters bool } -func NewCUPS(infoToDisplayName, prefixJobIDToJobTitle bool, displayNamePrefix string, printerAttributes []string, maxConnections uint, connectTimeout time.Duration, printerBlacklist []string, printerWhitelist []string, ignoreRawPrinters bool, ignoreClassPrinters bool) (*CUPS, error) { +func NewCUPS(infoToDisplayName, prefixJobIDToJobTitle bool, displayNamePrefix string, + printerAttributes, vendorPPDOptions []string, maxConnections uint, connectTimeout time.Duration, + printerBlacklist, printerWhitelist []string, ignoreRawPrinters bool, ignoreClassPrinters bool) (*CUPS, error) { if err := checkPrinterAttributes(printerAttributes); err != nil { return nil, err } @@ -150,7 +152,7 @@ func NewCUPS(infoToDisplayName, prefixJobIDToJobTitle bool, displayNamePrefix st if err != nil { return nil, err } - pc := newPPDCache(cc) + pc := newPPDCache(cc, vendorPPDOptions) systemTags, err := getSystemTags() if err != nil { diff --git a/cups/ppdcache.go b/cups/ppdcache.go index 17f2151..139b843 100644 --- a/cups/ppdcache.go +++ b/cups/ppdcache.go @@ -31,16 +31,18 @@ import ( // (1) maintains temporary file copies of PPDs for each printer // (2) updates those PPD files as necessary type ppdCache struct { - cc *cupsCore - cache map[string]*ppdCacheEntry - cacheMutex sync.RWMutex + cc *cupsCore + vendorPPDOptions []string + cache map[string]*ppdCacheEntry + cacheMutex sync.RWMutex } -func newPPDCache(cc *cupsCore) *ppdCache { +func newPPDCache(cc *cupsCore, vendorPPDOptions []string) *ppdCache { cache := make(map[string]*ppdCacheEntry) pc := ppdCache{ - cc: cc, - cache: cache, + cc: cc, + vendorPPDOptions: vendorPPDOptions, + cache: cache, } return &pc } @@ -76,7 +78,7 @@ func (pc *ppdCache) getPPDCacheEntry(printername string) (*cdd.PrinterDescriptio if err != nil { return nil, "", "", nil, err } - if err = pce.refresh(pc.cc); err != nil { + if err = pce.refresh(pc.cc, pc.vendorPPDOptions); err != nil { pce.free() return nil, "", "", nil, err } @@ -94,7 +96,7 @@ func (pc *ppdCache) getPPDCacheEntry(printername string) (*cdd.PrinterDescriptio return &description, manufacturer, model, duplexMap, nil } else { - if err := pce.refresh(pc.cc); err != nil { + if err := pce.refresh(pc.cc, pc.vendorPPDOptions); err != nil { delete(pc.cache, printername) pce.free() return nil, "", "", nil, err @@ -147,7 +149,7 @@ func (pce *ppdCacheEntry) free() { // refresh calls cupsGetPPD3() to refresh this PPD information, in // case CUPS has a new PPD for the printer. -func (pce *ppdCacheEntry) refresh(cc *cupsCore) error { +func (pce *ppdCacheEntry) refresh(cc *cupsCore, vendorPPDOptions []string) error { pce.mutex.Lock() defer pce.mutex.Unlock() @@ -178,7 +180,7 @@ func (pce *ppdCacheEntry) refresh(cc *cupsCore) error { return err } - description, manufacturer, model, duplexMap := translatePPD(w.String()) + description, manufacturer, model, duplexMap := translatePPD(w.String(), vendorPPDOptions) if description == nil || manufacturer == "" || model == "" { return errors.New("Failed to parse PPD") } diff --git a/cups/translate-ppd.go b/cups/translate-ppd.go index 3371c90..171218f 100644 --- a/cups/translate-ppd.go +++ b/cups/translate-ppd.go @@ -127,48 +127,80 @@ type entry struct { // translatePPD extracts a PrinterDescriptionSection, manufacturer string, model string, and DuplexVendorMap // from a PPD string. -func translatePPD(ppd string) (*cdd.PrinterDescriptionSection, string, string, lib.DuplexVendorMap) { +func translatePPD(ppd string, vendorPPDOptions []string) (*cdd.PrinterDescriptionSection, string, string, lib.DuplexVendorMap) { statements := ppdToStatements(ppd) openUIStatements, installables, uiConstraints, standAlones := groupStatements(statements) openUIStatements = filterConstraints(openUIStatements, installables, uiConstraints) entriesByMainKeyword, entriesByTranslation := openUIStatementsToEntries(openUIStatements) + consideredMainKeywords := make(map[string]struct{}) + pds := cdd.PrinterDescriptionSection{ VendorCapability: &[]cdd.VendorCapability{}, } + var duplexMap lib.DuplexVendorMap if e, exists := entriesByMainKeyword[ppdPageSize]; exists { pds.MediaSize = convertMediaSize(e) + consideredMainKeywords[e.mainKeyword] = struct{}{} } if e, exists := entriesByMainKeyword[ppdColorModel]; exists { pds.Color = convertColorPPD(e) + consideredMainKeywords[e.mainKeyword] = struct{}{} } else if e, exists := entriesByMainKeyword[ppdCMAndResolution]; exists { pds.Color = convertColorPPD(e) + consideredMainKeywords[e.mainKeyword] = struct{}{} } else if e, exists := entriesByMainKeyword[ppdSelectColor]; exists { pds.Color = convertColorPPD(e) + consideredMainKeywords[e.mainKeyword] = struct{}{} } if e, exists := entriesByMainKeyword[ppdDuplex]; exists { pds.Duplex, duplexMap = convertDuplex(e) + consideredMainKeywords[e.mainKeyword] = struct{}{} } else if e, exists := entriesByMainKeyword[ppdKMDuplex]; exists { pds.Duplex, duplexMap = convertDuplex(e) + consideredMainKeywords[e.mainKeyword] = struct{}{} } if e, exists := entriesByMainKeyword[ppdResolution]; exists { pds.DPI = convertDPI(e) + consideredMainKeywords[e.mainKeyword] = struct{}{} } if e, exists := entriesByMainKeyword[ppdOutputBin]; exists { *pds.VendorCapability = append(*pds.VendorCapability, *convertVendorCapability(e)) + consideredMainKeywords[e.mainKeyword] = struct{}{} } if jobType, exists := entriesByMainKeyword[ppdJobType]; exists { if lockedPrintPassword, exists := entriesByMainKeyword[ppdLockedPrintPassword]; exists { vc := convertRicohLockedPrintPassword(jobType, lockedPrintPassword) if vc != nil { *pds.VendorCapability = append(*pds.VendorCapability, *vc) + consideredMainKeywords[jobType.mainKeyword] = struct{}{} + consideredMainKeywords[lockedPrintPassword.mainKeyword] = struct{}{} } } } if e, exists := entriesByTranslation[ppdPrintQualityTranslation]; exists { *pds.VendorCapability = append(*pds.VendorCapability, *convertVendorCapability(e)) + consideredMainKeywords[e.mainKeyword] = struct{}{} + } + + vendorPPDOptionsMap := make(map[string]struct{}, len(vendorPPDOptions)) + for _, o := range vendorPPDOptions { + vendorPPDOptionsMap[o] = struct{}{} + } + _, translateAllVendorPPDOptions := vendorPPDOptionsMap["all"] + for _, e := range entriesByMainKeyword { + if _, exists := consideredMainKeywords[e.mainKeyword]; exists { + continue + } + if !translateAllVendorPPDOptions { + if _, exists := vendorPPDOptionsMap[e.mainKeyword]; !exists { + continue + } + } + *pds.VendorCapability = append(*pds.VendorCapability, *convertVendorCapability(e)) } + if len(*pds.VendorCapability) == 0 { // Don't generate invalid CDD JSON. pds.VendorCapability = nil diff --git a/cups/translate-ppd_test.go b/cups/translate-ppd_test.go index d361fcb..76815de 100644 --- a/cups/translate-ppd_test.go +++ b/cups/translate-ppd_test.go @@ -22,8 +22,8 @@ type testdata struct { Dm lib.DuplexVendorMap } -func translationTest(t *testing.T, ppd string, expected testdata) { - description, _, _, dm := translatePPD(ppd) +func translationTest(t *testing.T, ppd string, vendorPPDOptions []string, expected testdata) { + description, _, _, dm := translatePPD(ppd, vendorPPDOptions) actual := testdata{description, dm} if !reflect.DeepEqual(expected, actual) { e, _ := json.Marshal(expected) @@ -48,7 +48,7 @@ func TestTrPrintingSpeed(t *testing.T) { }, nil, } - translationTest(t, ppd, expected) + translationTest(t, ppd, []string{}, expected) } func TestTrMediaSize(t *testing.T) { @@ -77,7 +77,7 @@ func TestTrMediaSize(t *testing.T) { }, nil, } - translationTest(t, ppd, expected) + translationTest(t, ppd, []string{}, expected) } func TestTrColor(t *testing.T) { @@ -98,7 +98,7 @@ func TestTrColor(t *testing.T) { }, nil, } - translationTest(t, ppd, expected) + translationTest(t, ppd, []string{}, expected) ppd = `*PPD-Adobe: "4.3" *OpenUI *CMAndResolution/Print Color as Gray: PickOne @@ -123,7 +123,7 @@ func TestTrColor(t *testing.T) { }, nil, } - translationTest(t, ppd, expected) + translationTest(t, ppd, []string{}, expected) ppd = `*PPD-Adobe: "4.3" *OpenUI *CMAndResolution/Print Color as Gray: PickOne @@ -146,7 +146,7 @@ func TestTrColor(t *testing.T) { }, nil, } - translationTest(t, ppd, expected) + translationTest(t, ppd, []string{}, expected) ppd = `*PPD-Adobe: "4.3" *OpenUI *SelectColor/Select Color: PickOne @@ -167,7 +167,7 @@ func TestTrColor(t *testing.T) { }, nil, } - translationTest(t, ppd, expected) + translationTest(t, ppd, []string{}, expected) } func TestTrDuplex(t *testing.T) { @@ -191,7 +191,7 @@ func TestTrDuplex(t *testing.T) { cdd.DuplexLongEdge: "Duplex:DuplexNoTumble", }, } - translationTest(t, ppd, expected) + translationTest(t, ppd, []string{}, expected) } func TestTrKMDuplex(t *testing.T) { @@ -226,7 +226,7 @@ func TestTrKMDuplex(t *testing.T) { cdd.DuplexShortEdge: "KMDuplex:Booklet", }, } - translationTest(t, ppd, expected) + translationTest(t, ppd, []string{}, expected) ppd = `*PPD-Adobe: "4.3" *OpenUI *KMDuplex/Duplex: Boolean @@ -250,7 +250,7 @@ func TestTrKMDuplex(t *testing.T) { cdd.DuplexLongEdge: "KMDuplex:True", }, } - translationTest(t, ppd, expected) + translationTest(t, ppd, []string{}, expected) } func TestTrDPI(t *testing.T) { @@ -273,7 +273,7 @@ func TestTrDPI(t *testing.T) { }, nil, } - translationTest(t, ppd, expected) + translationTest(t, ppd, []string{}, expected) } func TestTrInputSlot(t *testing.T) { @@ -304,7 +304,7 @@ func TestTrInputSlot(t *testing.T) { }, nil, } - translationTest(t, ppd, expected) + translationTest(t, ppd, []string{}, expected) } func TestTrPrintQuality(t *testing.T) { @@ -334,7 +334,7 @@ func TestTrPrintQuality(t *testing.T) { }, nil, } - translationTest(t, ppd, expected) + translationTest(t, ppd, []string{}, expected) } func TestRicohLockedPrint(t *testing.T) { @@ -379,7 +379,7 @@ func TestRicohLockedPrint(t *testing.T) { }, nil, } - translationTest(t, ppd, expected) + translationTest(t, ppd, []string{}, expected) } func easyModelTest(t *testing.T, input, expected string) { @@ -416,3 +416,44 @@ func TestCleanupModel(t *testing.T) { easyModelTest(t, "LaserJet 4250 pcl3, hpcups 3.13.9", "LaserJet 4250") easyModelTest(t, "DesignJet T790 pcl, 1.0", "DesignJet T790") } + +func TestTrVendorPPDOptions(t *testing.T) { + ppd := `*PPD-Adobe: "4.3" +*OpenUI *CustomKey/Custom Translation: PickOne +*DefaultCustomKey: Some +*CustomKey None/Off: "" +*CustomKey Some/On: "" +*CustomKey Yes/Petunia: "" +*CloseUI: *CustomKey` + + expected := testdata{ + &cdd.PrinterDescriptionSection{ + VendorCapability: &[]cdd.VendorCapability{ + cdd.VendorCapability{ + ID: "CustomKey", + Type: cdd.VendorCapabilitySelect, + DisplayNameLocalized: cdd.NewLocalizedString("Custom Translation"), + SelectCap: &cdd.SelectCapability{ + Option: []cdd.SelectCapabilityOption{ + cdd.SelectCapabilityOption{"None", "", false, cdd.NewLocalizedString("Off")}, + cdd.SelectCapabilityOption{"Some", "", true, cdd.NewLocalizedString("On")}, + cdd.SelectCapabilityOption{"Yes", "", false, cdd.NewLocalizedString("Petunia")}, + }, + }, + }, + }, + }, + nil, + } + translationTest(t, ppd, []string{"CustomKey", "AnyOtherKey"}, expected) + translationTest(t, ppd, []string{"all"}, expected) + translationTest(t, ppd, []string{"CustomKey", "all"}, expected) + translationTest(t, ppd, []string{"AnyOtherKey", "all"}, expected) + + expected = testdata{ + &cdd.PrinterDescriptionSection{}, + nil, + } + translationTest(t, ppd, []string{}, expected) + translationTest(t, ppd, []string{"AnyOtherKey"}, expected) +} diff --git a/gcp-cups-connector/gcp-cups-connector.go b/gcp-cups-connector/gcp-cups-connector.go index f1105c3..d256f3d 100644 --- a/gcp-cups-connector/gcp-cups-connector.go +++ b/gcp-cups-connector/gcp-cups-connector.go @@ -155,7 +155,7 @@ func connector(context *cli.Context) int { return 1 } c, err := cups.NewCUPS(*config.CUPSCopyPrinterInfoToDisplayName, *config.PrefixJobIDToJobTitle, - config.DisplayNamePrefix, config.CUPSPrinterAttributes, config.CUPSMaxConnections, + config.DisplayNamePrefix, config.CUPSPrinterAttributes, config.CUPSVendorPPDOptions, config.CUPSMaxConnections, cupsConnectTimeout, config.PrinterBlacklist, config.PrinterWhitelist, *config.CUPSIgnoreRawPrinters, *config.CUPSIgnoreClassPrinters) if err != nil { diff --git a/lib/config_unix.go b/lib/config_unix.go index d89a2d5..3fab02c 100644 --- a/lib/config_unix.go +++ b/lib/config_unix.go @@ -135,6 +135,9 @@ type Config struct { // CUPS only: printer attributes to copy to GCP. CUPSPrinterAttributes []string `json:"cups_printer_attributes,omitempty"` + // CUPS only: non-standard PPD options to add as GCP vendor capabilities. + CUPSVendorPPDOptions []string `json:"cups_vendor_ppd_options,omitempty"` + // CUPS only: ignore printers with make/model 'Local Raw Printer'. CUPSIgnoreRawPrinters *bool `json:"cups_ignore_raw_printers,omitempty"` From fe125b8f24332616c4a1e025e2e36dd165221037 Mon Sep 17 00:00:00 2001 From: Jacob Marble Date: Fri, 29 Jul 2016 17:25:22 -0700 Subject: [PATCH 28/76] Update usage of codegangsta/cli package. (#283) Fixes #223 --- gcp-connector-util/gcp-cups-connector-util.go | 2 +- gcp-connector-util/init.go | 6 +- gcp-connector-util/main_unix.go | 2 +- gcp-connector-util/main_windows.go | 2 +- gcp-connector-util/monitor.go | 2 +- gcp-cups-connector/gcp-cups-connector.go | 71 ++++++++++--------- .../gcp-windows-connector.go | 13 ++-- lib/config.go | 2 +- lib/config_unix.go | 2 +- lib/config_windows.go | 4 +- 10 files changed, 54 insertions(+), 52 deletions(-) diff --git a/gcp-connector-util/gcp-cups-connector-util.go b/gcp-connector-util/gcp-cups-connector-util.go index 0a9f3f3..0e84003 100644 --- a/gcp-connector-util/gcp-cups-connector-util.go +++ b/gcp-connector-util/gcp-cups-connector-util.go @@ -16,10 +16,10 @@ import ( "strings" "sync" - "github.com/codegangsta/cli" "github.com/google/cloud-print-connector/cdd" "github.com/google/cloud-print-connector/gcp" "github.com/google/cloud-print-connector/lib" + "github.com/urfave/cli" ) var commonCommands = []cli.Command{ diff --git a/gcp-connector-util/init.go b/gcp-connector-util/init.go index 8bd7816..fdad878 100644 --- a/gcp-connector-util/init.go +++ b/gcp-connector-util/init.go @@ -20,9 +20,9 @@ import ( "strings" "time" - "github.com/codegangsta/cli" "github.com/google/cloud-print-connector/gcp" "github.com/google/cloud-print-connector/lib" + "github.com/urfave/cli" "golang.org/x/oauth2" ) @@ -360,8 +360,8 @@ func initConfigFile(context *cli.Context) { } else if context.IsSet("local-printing-enable") { localEnable = context.Bool("local-printing-enable") } else { - fmt.Println("\"Local printing\" means that clients print directly to the connector via local subnet,") - fmt.Println("and that an Internet connection is neither necessary nor used.") + fmt.Println("\"Local printing\" means that clients print directly to the connector via") + fmt.Println("local subnet, and that an Internet connection is neither necessary nor used.") localEnable = scanYesOrNo("Enable local printing?") } diff --git a/gcp-connector-util/main_unix.go b/gcp-connector-util/main_unix.go index 8efefda..edd0498 100644 --- a/gcp-connector-util/main_unix.go +++ b/gcp-connector-util/main_unix.go @@ -13,8 +13,8 @@ import ( "os" "time" - "github.com/codegangsta/cli" "github.com/google/cloud-print-connector/lib" + "github.com/urfave/cli" ) var unixInitFlags = []cli.Flag{ diff --git a/gcp-connector-util/main_windows.go b/gcp-connector-util/main_windows.go index 497c8d8..60b212a 100644 --- a/gcp-connector-util/main_windows.go +++ b/gcp-connector-util/main_windows.go @@ -14,8 +14,8 @@ import ( "os" "path/filepath" - "github.com/codegangsta/cli" "github.com/google/cloud-print-connector/lib" + "github.com/urfave/cli" "golang.org/x/sys/windows/svc" "golang.org/x/sys/windows/svc/eventlog" "golang.org/x/sys/windows/svc/mgr" diff --git a/gcp-connector-util/monitor.go b/gcp-connector-util/monitor.go index 52aaf24..b333d20 100644 --- a/gcp-connector-util/monitor.go +++ b/gcp-connector-util/monitor.go @@ -16,8 +16,8 @@ import ( "os" "time" - "github.com/codegangsta/cli" "github.com/google/cloud-print-connector/lib" + "github.com/urfave/cli" ) func monitorConnector(context *cli.Context) { diff --git a/gcp-cups-connector/gcp-cups-connector.go b/gcp-cups-connector/gcp-cups-connector.go index d256f3d..56fcf8c 100644 --- a/gcp-cups-connector/gcp-cups-connector.go +++ b/gcp-cups-connector/gcp-cups-connector.go @@ -10,6 +10,7 @@ package main import ( "encoding/json" + "errors" "fmt" "io" "io/ioutil" @@ -18,7 +19,6 @@ import ( "syscall" "time" - "github.com/codegangsta/cli" "github.com/coreos/go-systemd/journal" "github.com/google/cloud-print-connector/cups" "github.com/google/cloud-print-connector/gcp" @@ -28,6 +28,7 @@ import ( "github.com/google/cloud-print-connector/monitor" "github.com/google/cloud-print-connector/privet" "github.com/google/cloud-print-connector/xmpp" + "github.com/urfave/cli" ) func main() { @@ -42,17 +43,14 @@ func main() { Usage: "Log to STDERR, in addition to configured logging", }, } - app.Action = func(context *cli.Context) { - os.Exit(connector(context)) - } - app.RunAndExitOnError() + app.Action = connector + app.Run(os.Args) } -func connector(context *cli.Context) int { +func connector(context *cli.Context) error { config, configFilename, err := lib.GetConfig(context) if err != nil { - fmt.Fprintf(os.Stderr, "Failed to read config file: %s", err) - return 1 + return fmt.Errorf("Failed to read config file: %s", err) } logToJournal := *config.LogToJournal && journal.Enabled() @@ -70,8 +68,7 @@ func connector(context *cli.Context) int { 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 + return fmt.Errorf("Failed to start log roller: %s", err) } if logToConsole { @@ -82,8 +79,7 @@ func connector(context *cli.Context) int { logLevel, ok := log.LevelFromString(config.LogLevel) if !ok { - fmt.Fprintf(os.Stderr, "Log level %s is not recognized", config.LogLevel) - return 1 + return fmt.Errorf("Log level %s is not recognized", config.LogLevel) } log.SetLevel(logLevel) @@ -99,19 +95,22 @@ func connector(context *cli.Context) int { 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 + errStr := "Cannot run connector with both local_printing_enable and cloud_printing_enable set to false" + log.Fatal(errStr) + return errors.New(errStr) } if _, err := os.Stat(config.MonitorSocketFilename); !os.IsNotExist(err) { + var errStr string if err != nil { - log.Fatalf("Failed to stat monitor socket: %s", err) + errStr = fmt.Sprintf("Failed to stat monitor socket: %s", err) } else { - log.Fatalf( + errStr = fmt.Sprintf( "A connector is already running, or the monitoring socket %s wasn't cleaned up properly", config.MonitorSocketFilename) } - return 1 + log.Fatal(errStr) + return errors.New(errStr) } jobs := make(chan *lib.Job, 10) @@ -122,13 +121,15 @@ func connector(context *cli.Context) int { if config.CloudPrintingEnable { xmppPingTimeout, err := time.ParseDuration(config.XMPPPingTimeout) if err != nil { - log.Fatalf("Failed to parse xmpp ping timeout: %s", err) - return 1 + errStr := fmt.Sprintf("Failed to parse xmpp ping timeout: %s", err) + log.Fatal(errStr) + return errors.New(errStr) } xmppPingInterval, err := time.ParseDuration(config.XMPPPingInterval) if err != nil { - log.Fatalf("Failed to parse xmpp ping interval default: %s", err) - return 1 + errStr := fmt.Sprintf("Failed to parse xmpp ping interval default: %s", err) + log.Fatalf(errStr) + return errors.New(errStr) } g, err = gcp.NewGoogleCloudPrint(config.GCPBaseURL, config.RobotRefreshToken, @@ -137,22 +138,23 @@ func connector(context *cli.Context) int { config.GCPMaxConcurrentDownloads, jobs) if err != nil { log.Fatal(err) - return 1 + return err } 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 + return err } defer x.Quit() } cupsConnectTimeout, err := time.ParseDuration(config.CUPSConnectTimeout) if err != nil { - log.Fatalf("Failed to parse CUPS connect timeout: %s", err) - return 1 + errStr := fmt.Sprintf("Failed to parse CUPS connect timeout: %s", err) + log.Fatalf(errStr) + return errors.New(errStr) } c, err := cups.NewCUPS(*config.CUPSCopyPrinterInfoToDisplayName, *config.PrefixJobIDToJobTitle, config.DisplayNamePrefix, config.CUPSPrinterAttributes, config.CUPSVendorPPDOptions, config.CUPSMaxConnections, @@ -160,7 +162,7 @@ func connector(context *cli.Context) int { *config.CUPSIgnoreClassPrinters) if err != nil { log.Fatal(err) - return 1 + return err } defer c.Quit() @@ -173,39 +175,40 @@ func connector(context *cli.Context) int { } if err != nil { log.Fatal(err) - return 1 + return err } defer priv.Quit() } nativePrinterPollInterval, err := time.ParseDuration(config.NativePrinterPollInterval) if err != nil { - log.Fatalf("Failed to parse CUPS printer poll interval: %s", err) - return 1 + errStr := fmt.Sprintf("Failed to parse CUPS printer poll interval: %s", err) + log.Fatal(errStr) + return errors.New(errStr) } pm, err := manager.NewPrinterManager(c, g, priv, nativePrinterPollInterval, config.NativeJobQueueSize, *config.CUPSJobFullUsername, config.ShareScope, jobs, xmppNotifications) if err != nil { log.Fatal(err) - return 1 + return err } defer pm.Quit() m, err := monitor.NewMonitor(c, g, priv, pm, config.MonitorSocketFilename) if err != nil { log.Fatal(err) - return 1 + return err } defer m.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) + fmt.Println("Ready to rock as proxy '%s' and in local mode", config.ProxyName) } else { log.Infof("Ready to rock as proxy '%s'", config.ProxyName) - fmt.Printf("Ready to rock as proxy '%s'\n", config.ProxyName) + fmt.Println("Ready to rock as proxy '%s'", config.ProxyName) } } else { log.Info("Ready to rock in local-only mode") @@ -218,7 +221,7 @@ func connector(context *cli.Context) int { fmt.Println("") fmt.Println("Shutting down") - return 0 + return nil } // Blocks until Ctrl-C or SIGTERM. diff --git a/gcp-windows-connector/gcp-windows-connector.go b/gcp-windows-connector/gcp-windows-connector.go index 1eb3153..3aee9b2 100644 --- a/gcp-windows-connector/gcp-windows-connector.go +++ b/gcp-windows-connector/gcp-windows-connector.go @@ -14,7 +14,7 @@ import ( "os" "time" - "github.com/codegangsta/cli" + "github.com/urfave/cli" "github.com/google/cloud-print-connector/gcp" "github.com/google/cloud-print-connector/lib" "github.com/google/cloud-print-connector/log" @@ -33,7 +33,7 @@ func main() { app.Flags = []cli.Flag{ lib.ConfigFilenameFlag, } - app.Action = RunService + app.Action = runService app.Run(os.Args) } @@ -53,19 +53,18 @@ type service struct { interactive bool } -func RunService(context *cli.Context) { +func runService(context *cli.Context) error { interactive, err := svc.IsAnInteractiveSession() if err != nil { - fmt.Fprintf(os.Stderr, "Failed to detect interactive session: %s\n", err) - os.Exit(1) + return fmt.Errorf("Failed to detect interactive session: %s", err) } s := service{context, interactive} if interactive { - debug.Run(lib.ConnectorName, &s) + return debug.Run(lib.ConnectorName, &s) } else { - svc.Run(lib.ConnectorName, &s) + return svc.Run(lib.ConnectorName, &s) } } diff --git a/lib/config.go b/lib/config.go index 6d42bb8..5d024fa 100644 --- a/lib/config.go +++ b/lib/config.go @@ -15,7 +15,7 @@ import ( "reflect" "runtime" - "github.com/codegangsta/cli" + "github.com/urfave/cli" ) const ( diff --git a/lib/config_unix.go b/lib/config_unix.go index 3fab02c..a428a02 100644 --- a/lib/config_unix.go +++ b/lib/config_unix.go @@ -13,7 +13,7 @@ import ( "path/filepath" "reflect" - "github.com/codegangsta/cli" + "github.com/urfave/cli" "launchpad.net/go-xdg/v0" ) diff --git a/lib/config_windows.go b/lib/config_windows.go index e640e9b..fe4eb5e 100644 --- a/lib/config_windows.go +++ b/lib/config_windows.go @@ -12,7 +12,7 @@ import ( "os" "path/filepath" - "github.com/codegangsta/cli" + "github.com/urfave/cli" ) const ( @@ -136,7 +136,7 @@ var DefaultConfig = Config{ "Microsoft XPS Document Writer", "Google Cloud Printer", }, - PrinterWhitelist: []string{}, + PrinterWhitelist: []string{}, LocalPrintingEnable: true, CloudPrintingEnable: false, LogLevel: "INFO", From 84508dfbdbf2caf2bc31be1989d6a7aaaef64ff0 Mon Sep 17 00:00:00 2001 From: Adam Goode Date: Sat, 30 Jul 2016 17:26:44 -0400 Subject: [PATCH 29/76] Add systemd unit file to repository (#274) This file should be usable as-is with little to no customization required. Fixes #242 --- systemd/cloud-print-connector.service | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 systemd/cloud-print-connector.service diff --git a/systemd/cloud-print-connector.service b/systemd/cloud-print-connector.service new file mode 100644 index 0000000..9cc5b90 --- /dev/null +++ b/systemd/cloud-print-connector.service @@ -0,0 +1,19 @@ +# Copyright 2016 Google Inc. All rights reserved. +# +# Use of this source code is governed by a BSD-style +# license that can be found in the LICENSE file or at +# https://developers.google.com/open-source/licenses/bsd + +[Unit] +Description=Google Cloud Print Connector +Documentation="https://github.com/google/cloud-print-connector" +After=cups.service avahi-daemon.service network-online.target +Wants=cups.service avahi-daemon.service network-online.target + +[Service] +ExecStart=/opt/cloud-print-connector/gcp-cups-connector -config-filename /opt/cloud-print-connector/gcp-cups-connector.config.json +Restart=on-failure +User=cloud-print-connector + +[Install] +WantedBy=multi-user.target From e7a886a5d461f881bfc75be510d898e15c049f45 Mon Sep 17 00:00:00 2001 From: Maciej Mucha Date: Wed, 3 Aug 2016 16:10:18 +0200 Subject: [PATCH 30/76] Mark offline printers as stopped (#285) * detect printer offline state on Windows * detect printer offline state with CUPS --- cups/translate-attrs.go | 11 +++++++++++ winspool/win32.go | 22 ++++++++++++++++++++++ winspool/winspool.go | 11 ++++++++--- 3 files changed, 41 insertions(+), 3 deletions(-) diff --git a/cups/translate-attrs.go b/cups/translate-attrs.go index 74eeb83..396ad4e 100644 --- a/cups/translate-attrs.go +++ b/cups/translate-attrs.go @@ -67,6 +67,17 @@ func getUUID(printerTags map[string][]string) string { } func getState(printerTags map[string][]string) cdd.CloudDeviceStateType { + // Some CUPS backends (e.g. usb-darwin) add offline-report + // to printer-state-reasons when the printer is offline/disconnected + reasons, exists := printerTags[attrPrinterStateReasons] + if exists && len(reasons) > 0 { + for _, reason := range reasons { + if reason == "offline-report" { + return cdd.CloudDeviceStateStopped + } + } + } + if s, ok := printerTags[attrPrinterState]; ok { switch s[0] { case "3": diff --git a/winspool/win32.go b/winspool/win32.go index d97897e..0a53e5a 100644 --- a/winspool/win32.go +++ b/winspool/win32.go @@ -99,6 +99,24 @@ const ( REG_QWORD_LITTLE_ENDIAN = 11 ) +// PRINTER_INFO_2 attribute values +const ( + PRINTER_ATTRIBUTE_QUEUED uint32 = 0x00000001 + PRINTER_ATTRIBUTE_DIRECT uint32 = 0x00000002 + PRINTER_ATTRIBUTE_DEFAULT uint32 = 0x00000004 + PRINTER_ATTRIBUTE_SHARED uint32 = 0x00000008 + PRINTER_ATTRIBUTE_NETWORK uint32 = 0x00000010 + PRINTER_ATTRIBUTE_HIDDEN uint32 = 0x00000020 + PRINTER_ATTRIBUTE_LOCAL uint32 = 0x00000040 + PRINTER_ATTRIBUTE_ENABLE_DEVQ uint32 = 0x00000080 + PRINTER_ATTRIBUTE_KEEPPRINTEDJOBS uint32 = 0x00000100 + PRINTER_ATTRIBUTE_DO_COMPLETE_FIRST uint32 = 0x00000200 + PRINTER_ATTRIBUTE_WORK_OFFLINE uint32 = 0x00000400 + PRINTER_ATTRIBUTE_ENABLE_BIDI uint32 = 0x00000800 + PRINTER_ATTRIBUTE_RAW_ONLY uint32 = 0x00001000 + PRINTER_ATTRIBUTE_PUBLISHED uint32 = 0x00002000 +) + // PRINTER_INFO_2 status values. const ( PRINTER_STATUS_PAUSED uint32 = 0x00000001 @@ -175,6 +193,10 @@ func (pi *PrinterInfo2) GetDevMode() *DevMode { return pi.pDevMode } +func (pi *PrinterInfo2) GetAttributes() uint32 { + return pi.attributes +} + func (pi *PrinterInfo2) GetStatus() uint32 { return pi.status } diff --git a/winspool/winspool.go b/winspool/winspool.go index b00b129..b1018f5 100644 --- a/winspool/winspool.go +++ b/winspool/winspool.go @@ -89,7 +89,7 @@ func getSystemTags() (map[string]string, error) { return tags, nil } -func convertPrinterState(wsStatus uint32) *cdd.PrinterStateSection { +func convertPrinterState(wsStatus uint32, wsAttributes uint32) *cdd.PrinterStateSection { state := cdd.PrinterStateSection{ State: cdd.CloudDeviceStateIdle, VendorState: &cdd.VendorState{}, @@ -154,7 +154,12 @@ func convertPrinterState(wsStatus uint32) *cdd.PrinterStateSection { } state.VendorState.Item = append(state.VendorState.Item, vs) } - if wsStatus&PRINTER_STATUS_OFFLINE != 0 { + + // If PRINTER_ATTRIBUTE_WORK_OFFLINE is set + // spooler won't despool any jobs to the printer. + // At least for some USB printers, this flag is controlled + // automatically by the system depending on the state of physical connection. + if wsStatus&PRINTER_STATUS_OFFLINE != 0 || wsAttributes&PRINTER_ATTRIBUTE_WORK_OFFLINE != 0 { state.State = cdd.CloudDeviceStateStopped vs := cdd.VendorStateItem{ State: cdd.VendorStateError, @@ -318,7 +323,7 @@ func (ws *WinSpool) GetPrinters() ([]lib.Printer, error) { UUID: printerName, // TODO: Add something unique from host. Manufacturer: manufacturer, Model: model, - State: convertPrinterState(pi2.GetStatus()), + State: convertPrinterState(pi2.GetStatus(), pi2.GetAttributes()), Description: &cdd.PrinterDescriptionSection{}, Tags: map[string]string{ "printer-location": pi2.GetLocation(), From a588dde719937e7d0a7f0bb12f19422d182486cf Mon Sep 17 00:00:00 2001 From: arastooz Date: Wed, 3 Aug 2016 13:21:52 -0700 Subject: [PATCH 31/76] Change the Windows installer to install the config file to CommonAppDataFolder instead of ProgramFilesFolder so that it is easier to modify. (#291) --- wix/README.md | 6 +++++ wix/windows-connector.wxs | 48 +++++++++++++++++++++++++-------------- 2 files changed, 37 insertions(+), 17 deletions(-) diff --git a/wix/README.md b/wix/README.md index 1340c0b..f024bac 100644 --- a/wix/README.md +++ b/wix/README.md @@ -57,3 +57,9 @@ will open a console window to initialize the connector. The following public properties may be set during install of the MSI (see https://msdn.microsoft.com/en-us/library/windows/desktop/aa370912(v=vs.85).aspx) * CONFIGFILE = Path of connector config file to use instead of running gcp-connector-util init during install + +## Modifying the Config File after install +The installer will create (or copy) the config file specified to the Common +Application Data directory at %PROGRAMDATA%\Google\Cloud Print Connector. +This is the file that is used by the connector. This file can be modified +and the service restarted to change the configuration. diff --git a/wix/windows-connector.wxs b/wix/windows-connector.wxs index 65f732f..dfb9ab4 100644 --- a/wix/windows-connector.wxs +++ b/wix/windows-connector.wxs @@ -10,8 +10,10 @@ - + @@ -36,6 +38,7 @@ Level="1"> + @@ -43,10 +46,15 @@ - + + + + + + @@ -64,7 +72,8 @@ Start="auto" Type="ownProcess" ErrorControl="normal" - Vital="yes"> + Vital="yes" + Arguments='--config-filename="[CloudPrintDataFolder]gcp-windows-connector.config.json"'> - - - - + + + + + + + Date: Wed, 3 Aug 2016 22:47:59 +0200 Subject: [PATCH 32/76] Wait for job to finish before releasing it from spooler (#289) --- cups/cups.go | 6 ++++++ manager/printermanager.go | 8 ++++++++ winspool/winspool.go | 19 ++++++++++++++----- 3 files changed, 28 insertions(+), 5 deletions(-) diff --git a/cups/cups.go b/cups/cups.go index 98a4ef4..7371528 100644 --- a/cups/cups.go +++ b/cups/cups.go @@ -632,3 +632,9 @@ func checkPrinterAttributes(printerAttributes []string) error { return nil } + +// The following functions are not relevant to CUPS printing, but are required by the NativePrintSystem interface. + +func (c *CUPS) ReleaseJob(printerName string, jobID uint32) error { + return nil +} diff --git a/manager/printermanager.go b/manager/printermanager.go index 20f893f..9f88219 100644 --- a/manager/printermanager.go +++ b/manager/printermanager.go @@ -29,6 +29,7 @@ type NativePrintSystem interface { GetPrinters() ([]lib.Printer, error) GetJobState(printerName string, jobID uint32) (*cdd.PrintJobStateDiff, error) Print(printer *lib.Printer, fileName, title, user, gcpJobID string, ticket *cdd.CloudJobTicket) (uint32, error) + ReleaseJob(printerName string, jobID uint32) error RemoveCachedPPD(printerName string) } @@ -401,6 +402,7 @@ func (pm *PrinterManager) printJob(nativePrinterName, filename, title, user, job ticker := time.NewTicker(time.Second) defer ticker.Stop() + defer pm.releaseJob(printer.Name, nativeJobID, jobID) for _ = range ticker.C { nativeState, err := pm.native.GetJobState(printer.Name, nativeJobID) @@ -440,6 +442,12 @@ func (pm *PrinterManager) printJob(nativePrinterName, filename, title, user, job } } +func (pm *PrinterManager)releaseJob(printerName string, nativeJobID uint32, jobID string) { + if err := pm.native.ReleaseJob(printerName, nativeJobID); err != nil { + log.ErrorJob(jobID, err) + } +} + // GetJobStats returns information that is useful for monitoring // the connector. func (pm *PrinterManager) GetJobStats() (uint, uint, uint, error) { diff --git a/winspool/winspool.go b/winspool/winspool.go index b1018f5..6618d18 100644 --- a/winspool/winspool.go +++ b/winspool/winspool.go @@ -683,10 +683,6 @@ func (c *jobContext) free() error { if err != nil { return err } - err = c.hPrinter.SetJobCommand(c.jobID, JOB_CONTROL_RELEASE) - if err != nil { - return err - } err = c.hDC.EndDoc() if err != nil { return err @@ -908,7 +904,20 @@ func (ws *WinSpool) Print(printer *lib.Printer, fileName, title, user, gcpJobID return uint32(jobContext.jobID), nil } +func (ws *WinSpool) ReleaseJob(printerName string, jobID uint32) error { + hPrinter, err := OpenPrinter(printerName) + if err != nil { + return err + } + + err = hPrinter.SetJobCommand(int32(jobID), JOB_CONTROL_RELEASE) + if err != nil { + return err + } + + return nil +} + // The following functions are not relevant to Windows printing, but are required by the NativePrintSystem interface. -func (ws *WinSpool) Quit() {} func (ws *WinSpool) RemoveCachedPPD(printerName string) {} From c4a2feb096cc6b90575586abdcfa910abe760caa Mon Sep 17 00:00:00 2001 From: arastooz Date: Thu, 4 Aug 2016 09:46:06 -0700 Subject: [PATCH 33/76] Add custom action that deletes GCP printers during uninstall. (#292) Tested upgrade path and uninstall to make sure this runs during only uninstall. --- wix/windows-connector.wxs | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/wix/windows-connector.wxs b/wix/windows-connector.wxs index dfb9ab4..3441d3c 100644 --- a/wix/windows-connector.wxs +++ b/wix/windows-connector.wxs @@ -31,7 +31,7 @@ - + - NOT CONFIGFILE and NOT Installed + NOT CONFIGFILE and NOT Installed and NOT WIX_UPGRADE_DETECTED + + + + DELETEPRINTERS="YES" AND $ConfigFile=2 + + + + + + + From e1a47b7a81383b4aa92b431485dd95bd6c059199 Mon Sep 17 00:00:00 2001 From: Jacob Marble Date: Fri, 5 Aug 2016 13:17:13 -0700 Subject: [PATCH 34/76] Fix some CLI error messages. (#293) Fixes #223 --- gcp-connector-util/gcp-cups-connector-util.go | 204 +++++++++++------- gcp-connector-util/init.go | 118 +++++----- gcp-connector-util/main_unix.go | 4 - gcp-connector-util/main_windows.go | 64 +++--- gcp-connector-util/monitor.go | 21 +- gcp-cups-connector/gcp-cups-connector.go | 4 +- 6 files changed, 238 insertions(+), 177 deletions(-) diff --git a/gcp-connector-util/gcp-cups-connector-util.go b/gcp-connector-util/gcp-cups-connector-util.go index 0e84003..4b713f5 100644 --- a/gcp-connector-util/gcp-cups-connector-util.go +++ b/gcp-connector-util/gcp-cups-connector-util.go @@ -10,9 +10,9 @@ package main import ( "encoding/json" + "errors" "fmt" "io/ioutil" - "log" "strings" "sync" @@ -161,120 +161,135 @@ var commonCommands = []cli.Command{ } // getConfig returns a config object -func getConfig(context *cli.Context) *lib.Config { +func getConfig(context *cli.Context) (*lib.Config, error) { config, _, err := lib.GetConfig(context) if err != nil { - log.Fatalln(err) + return nil, err } - return config + return config, nil } // getGCP returns a GoogleCloudPrint object -func getGCP(config *lib.Config) *gcp.GoogleCloudPrint { - gcp, err := gcp.NewGoogleCloudPrint(config.GCPBaseURL, config.RobotRefreshToken, +func getGCP(config *lib.Config) (*gcp.GoogleCloudPrint, error) { + return gcp.NewGoogleCloudPrint(config.GCPBaseURL, config.RobotRefreshToken, config.UserRefreshToken, config.ProxyName, config.GCPOAuthClientID, config.GCPOAuthClientSecret, config.GCPOAuthAuthURL, config.GCPOAuthTokenURL, 0, nil) - if err != nil { - log.Fatalln(err) - } - return gcp } // backfillConfigFile opens the config file, adds all missing keys // and default values, then writes the config file back. -func backfillConfigFile(context *cli.Context) { +func backfillConfigFile(context *cli.Context) error { config, cfBefore, err := lib.GetConfig(context) if err != nil { - log.Fatalln(err) + return err } if cfBefore == "" { - fmt.Println("Could not find a config file to backfill") - return + return fmt.Errorf("Could not find a config file to backfill") } // Same config in []byte format. configRaw, err := ioutil.ReadFile(cfBefore) if err != nil { - log.Fatalln(err) + return err } // Same config in map format so that we can detect missing keys. var configMap map[string]interface{} if err = json.Unmarshal(configRaw, &configMap); err != nil { - log.Fatalln(err) + return err } if cfWritten, err := config.Backfill(configMap).ToFile(context); err != nil { - fmt.Printf("Failed to write config file: %s\n", err) + return fmt.Errorf("Failed to write config file: %s", err) } else { fmt.Printf("Wrote %s\n", cfWritten) } + return nil } // sparseConfigFile opens the config file, removes most keys // that have default values, then writes the config file back. -func sparseConfigFile(context *cli.Context) { +func sparseConfigFile(context *cli.Context) error { config, cfBefore, err := lib.GetConfig(context) if err != nil { - log.Fatalln(err) + return err } if cfBefore == "" { - fmt.Println("Could not find a config file to sparse") - return + return errors.New("Could not find a config file to sparse") } if cfWritten, err := config.Sparse(context).ToFile(context); err != nil { - fmt.Printf("Failed to write config file: %s\n", err) + return fmt.Errorf("Failed to write config file: %s\n", err) } else { fmt.Printf("Wrote %s\n", cfWritten) } + return nil } // deleteAllGCPPrinters finds all GCP printers associated with this // connector, deletes them from GCP. -func deleteAllGCPPrinters(context *cli.Context) { - config := getConfig(context) - gcp := getGCP(config) +func deleteAllGCPPrinters(context *cli.Context) error { + config, err := getConfig(context) + if err != nil { + return err + } + gcp, err := getGCP(config) + if err != nil { + return err + } printers, err := gcp.List() if err != nil { - log.Fatalln(err) + return err } var wg sync.WaitGroup for gcpID, name := range printers { wg.Add(1) go func(gcpID, name string) { + defer wg.Done() err := gcp.Delete(gcpID) if err != nil { fmt.Printf("Failed to delete %s \"%s\": %s\n", gcpID, name, err) } else { fmt.Printf("Deleted %s \"%s\" from GCP\n", gcpID, name) } - wg.Done() }(gcpID, name) } wg.Wait() + return nil } // deleteGCPJob deletes one GCP job -func deleteGCPJob(context *cli.Context) { - config := getConfig(context) - gcp := getGCP(config) +func deleteGCPJob(context *cli.Context) error { + config, err := getConfig(context) + if err != nil { + return err + } + gcp, err := getGCP(config) + if err != nil { + return err + } - err := gcp.DeleteJob(context.String("job-id")) + err = gcp.DeleteJob(context.String("job-id")) if err != nil { - fmt.Printf("Failed to delete GCP job %s: %s\n", context.String("job-id"), err) - } else { - fmt.Printf("Deleted GCP job %s\n", context.String("job-id")) + return fmt.Errorf("Failed to delete GCP job %s: %s\n", context.String("job-id"), err) } + fmt.Printf("Deleted GCP job %s\n", context.String("job-id")) + return nil } // cancelGCPJob cancels one GCP job -func cancelGCPJob(context *cli.Context) { - config := getConfig(context) - gcp := getGCP(config) +func cancelGCPJob(context *cli.Context) error { + config, err := getConfig(context) + if err != nil { + return err + } + gcp, err := getGCP(config) + if err != nil { + return err + } cancelState := cdd.PrintJobStateDiff{ State: &cdd.JobState{ @@ -283,23 +298,29 @@ func cancelGCPJob(context *cli.Context) { }, } - err := gcp.Control(context.String("job-id"), &cancelState) + err = gcp.Control(context.String("job-id"), &cancelState) if err != nil { - fmt.Printf("Failed to cancel GCP job %s: %s\n", context.String("job-id"), err) - } else { - fmt.Printf("Canceled GCP job %s\n", context.String("job-id")) + return fmt.Errorf("Failed to cancel GCP job %s: %s", context.String("job-id"), err) } + fmt.Printf("Canceled GCP job %s\n", context.String("job-id")) + return nil } // deleteAllGCPPrinterJobs finds all GCP printer jobs associated with a // a given printer id and deletes them. -func deleteAllGCPPrinterJobs(context *cli.Context) { - config := getConfig(context) - gcp := getGCP(config) +func deleteAllGCPPrinterJobs(context *cli.Context) error { + config, err := getConfig(context) + if err != nil { + return err + } + gcp, err := getGCP(config) + if err != nil { + return err + } jobs, err := gcp.Fetch(context.String("printer-id")) if err != nil { - log.Fatalln(err) + return err } if len(jobs) == 0 { @@ -322,17 +343,24 @@ func deleteAllGCPPrinterJobs(context *cli.Context) { for _ = range jobs { <-ch } + return nil } // cancelAllGCPPrinterJobs finds all GCP printer jobs associated with a // a given printer id and cancels them. -func cancelAllGCPPrinterJobs(context *cli.Context) { - config := getConfig(context) - gcp := getGCP(config) +func cancelAllGCPPrinterJobs(context *cli.Context) error { + config, err := getConfig(context) + if err != nil { + return err + } + gcp, err := getGCP(config) + if err != nil { + return err + } jobs, err := gcp.Fetch(context.String("printer-id")) if err != nil { - log.Fatalln(err) + return err } if len(jobs) == 0 { @@ -362,16 +390,23 @@ func cancelAllGCPPrinterJobs(context *cli.Context) { for _ = range jobs { <-ch } + return nil } // showGCPPrinterStatus shows the current status of a GCP printer and it's jobs -func showGCPPrinterStatus(context *cli.Context) { - config := getConfig(context) - gcp := getGCP(config) +func showGCPPrinterStatus(context *cli.Context) error { + config, err := getConfig(context) + if err != nil { + return err + } + gcp, err := getGCP(config) + if err != nil { + return err + } printer, _, err := gcp.Printer(context.String("printer-id")) if err != nil { - log.Fatalln(err) + return err } fmt.Println("Name:", printer.DefaultDisplayName) @@ -379,7 +414,7 @@ func showGCPPrinterStatus(context *cli.Context) { jobs, err := gcp.Jobs(context.String("printer-id")) if err != nil { - log.Fatalln(err) + return err } // Only init common states. Unusual states like DRAFT will only be shown @@ -401,12 +436,19 @@ func showGCPPrinterStatus(context *cli.Context) { for state, count := range jobStateCounts { fmt.Println(" ", state, ":", count) } + return nil } // shareGCPPrinter shares a GCP printer -func shareGCPPrinter(context *cli.Context) { - config := getConfig(context) - gcpConn := getGCP(config) +func shareGCPPrinter(context *cli.Context) error { + config, err := getConfig(context) + if err != nil { + return err + } + gcpConn, err := getGCP(config) + if err != nil { + return err + } var role gcp.Role switch strings.ToUpper(context.String("role")) { @@ -415,11 +457,10 @@ func shareGCPPrinter(context *cli.Context) { case "MANAGER": role = gcp.Manager default: - fmt.Println("role should be user or manager.") - return + return fmt.Errorf("role should be user or manager.") } - err := gcpConn.Share(context.String("printer-id"), context.String("email"), + err = gcpConn.Share(context.String("printer-id"), context.String("email"), role, context.Bool("skip-notification"), context.Bool("public")) var sharedWith string if context.Bool("public") { @@ -428,18 +469,24 @@ func shareGCPPrinter(context *cli.Context) { sharedWith = context.String("email") } if err != nil { - fmt.Printf("Failed to share GCP printer %s with %s: %s\n", context.String("printer-id"), sharedWith, err) - } else { - fmt.Printf("Shared GCP printer %s with %s\n", context.String("printer-id"), sharedWith) + return fmt.Errorf("Failed to share GCP printer %s with %s: %s\n", context.String("printer-id"), sharedWith, err) } + fmt.Printf("Shared GCP printer %s with %s\n", context.String("printer-id"), sharedWith) + return nil } // unshareGCPPrinter unshares a GCP printer. -func unshareGCPPrinter(context *cli.Context) { - config := getConfig(context) - gcpConn := getGCP(config) +func unshareGCPPrinter(context *cli.Context) error { + config, err := getConfig(context) + if err != nil { + return err + } + gcpConn, err := getGCP(config) + if err != nil { + return err + } - err := gcpConn.Unshare(context.String("printer-id"), context.String("email"), context.Bool("public")) + err = gcpConn.Unshare(context.String("printer-id"), context.String("email"), context.Bool("public")) var sharedWith string if context.Bool("public") { sharedWith = "public" @@ -447,16 +494,22 @@ func unshareGCPPrinter(context *cli.Context) { sharedWith = context.String("email") } if err != nil { - fmt.Printf("Failed to unshare GCP printer %s with %s: %s\n", context.String("printer-id"), sharedWith, err) - } else { - fmt.Printf("Unshared GCP printer %s with %s\n", context.String("printer-id"), sharedWith) + return fmt.Errorf("Failed to unshare GCP printer %s with %s: %s\n", context.String("printer-id"), sharedWith, err) } + fmt.Printf("Unshared GCP printer %s with %s\n", context.String("printer-id"), sharedWith) + return nil } // updateGCPPrinter updates settings for a GCP printer. -func updateGCPPrinter(context *cli.Context) { - config := getConfig(context) - gcpConn := getGCP(config) +func updateGCPPrinter(context *cli.Context) error { + config, err := getConfig(context) + if err != nil { + return err + } + gcpConn, err := getGCP(config) + if err != nil { + return err + } var diff lib.PrinterDiff diff.Printer = lib.Printer{GCPID: context.String("printer-id")} @@ -472,10 +525,11 @@ func updateGCPPrinter(context *cli.Context) { diff.Printer.DailyQuota = context.Int("daily-quota") diff.DailyQuotaChanged = true } - err := gcpConn.Update(&diff) + err = gcpConn.Update(&diff) if err != nil { - fmt.Printf("Failed to update GCP printer %s: %s", context.String("printer-id"), err) + return fmt.Errorf("Failed to update GCP printer %s: %s", context.String("printer-id"), err) } else { fmt.Printf("Updated GCP printer %s", context.String("printer-id")) } + return nil } diff --git a/gcp-connector-util/init.go b/gcp-connector-util/init.go index fdad878..0e053e1 100644 --- a/gcp-connector-util/init.go +++ b/gcp-connector-util/init.go @@ -11,7 +11,6 @@ package main import ( "encoding/json" "fmt" - "log" "net/http" "net/url" "os" @@ -145,14 +144,14 @@ func postWithRetry(url string, data url.Values) (*http.Response, error) { // getUserClientFromUser follows the token acquisition steps outlined here: // https://developers.google.com/identity/protocols/OAuth2ForDevices -func getUserClientFromUser(context *cli.Context) (*http.Client, string) { +func getUserClientFromUser(context *cli.Context) (*http.Client, string, error) { form := url.Values{ "client_id": {context.String("gcp-oauth-client-id")}, "scope": {gcp.ScopeCloudPrint}, } response, err := postWithRetry(gcpOAuthDeviceCodeURL, form) if err != nil { - log.Fatalln(err) + return nil, "", err } var r struct { @@ -170,7 +169,7 @@ func getUserClientFromUser(context *cli.Context) (*http.Client, string) { return pollOAuthConfirmation(context, r.DeviceCode, r.Interval) } -func pollOAuthConfirmation(context *cli.Context, deviceCode string, interval int) (*http.Client, string) { +func pollOAuthConfirmation(context *cli.Context, deviceCode string, interval int) (*http.Client, string, error) { config := oauth2.Config{ ClientID: context.String("gcp-oauth-client-id"), ClientSecret: context.String("gcp-oauth-client-secret"), @@ -193,7 +192,7 @@ func pollOAuthConfirmation(context *cli.Context, deviceCode string, interval int } response, err := postWithRetry(gcpOAuthTokenPollURL, form) if err != nil { - log.Fatalln(err) + return nil, "", err } var r struct { @@ -209,12 +208,12 @@ func pollOAuthConfirmation(context *cli.Context, deviceCode string, interval int token := &oauth2.Token{RefreshToken: r.RefreshToken} client := config.Client(oauth2.NoContext, token) client.Timeout = context.Duration("gcp-api-timeout") - return client, r.RefreshToken + return client, r.RefreshToken, nil case "authorization_pending": case "slow_down": interval *= 2 default: - log.Fatalln(err) + return nil, "", err } } @@ -242,17 +241,17 @@ func getUserClientFromToken(context *cli.Context) *http.Client { } // initRobotAccount creates a GCP robot account for this connector. -func initRobotAccount(context *cli.Context, userClient *http.Client) (string, string) { +func initRobotAccount(context *cli.Context, userClient *http.Client) (string, string, error) { params := url.Values{} params.Set("oauth_client_id", context.String("gcp-oauth-client-id")) url := fmt.Sprintf("%s%s?%s", lib.DefaultConfig.GCPBaseURL, "createrobot", params.Encode()) response, err := userClient.Get(url) if err != nil { - log.Fatalln(err) + return "", "", err } if response.StatusCode != http.StatusOK { - log.Fatalf("Failed to initialize robot account: %s\n", response.Status) + return "", "", fmt.Errorf("Failed to initialize robot account: %s", response.Status) } var robotInit struct { @@ -263,16 +262,16 @@ func initRobotAccount(context *cli.Context, userClient *http.Client) (string, st } if err = json.NewDecoder(response.Body).Decode(&robotInit); err != nil { - log.Fatalln(err) + return "", "", err } if !robotInit.Success { - log.Fatalf("Failed to initialize robot account: %s\n", robotInit.Message) + return "", "", fmt.Errorf("Failed to initialize robot account: %s", robotInit.Message) } - return robotInit.XMPPJID, robotInit.AuthCode + return robotInit.XMPPJID, robotInit.AuthCode, nil } -func verifyRobotAccount(context *cli.Context, authCode string) string { +func verifyRobotAccount(context *cli.Context, authCode string) (string, error) { config := &oauth2.Config{ ClientID: context.String("gcp-oauth-client-id"), ClientSecret: context.String("gcp-oauth-client-secret"), @@ -286,51 +285,48 @@ func verifyRobotAccount(context *cli.Context, authCode string) string { token, err := config.Exchange(oauth2.NoContext, authCode) if err != nil { - log.Fatalln(err) + return "", err } - return token.RefreshToken + return token.RefreshToken, nil } -func createRobotAccount(context *cli.Context, userClient *http.Client) (string, string) { - xmppJID, authCode := initRobotAccount(context, userClient) - token := verifyRobotAccount(context, authCode) - - return xmppJID, token -} - -func writeConfigFile(context *cli.Context, config *lib.Config) string { - if configFilename, err := config.Sparse(context).ToFile(context); err != nil { - log.Fatalln(err) - } else { - return configFilename +func createRobotAccount(context *cli.Context, userClient *http.Client) (string, string, error) { + xmppJID, authCode, err := initRobotAccount(context, userClient) + if err != nil { + return "", "", err } - panic("unreachable") + token, err := verifyRobotAccount(context, authCode) + if err != nil { + return "", "", err + } + + return xmppJID, token, nil } -func scanNonEmptyString(prompt string) string { +func scanNonEmptyString(prompt string) (string, error) { for { var answer string fmt.Println(prompt) if length, err := fmt.Scan(&answer); err != nil { - log.Fatalln(err) + return "", err } else if length > 0 { fmt.Println("") - return answer + return answer, nil } } panic("unreachable") } -func scanYesOrNo(question string) bool { +func scanYesOrNo(question string) (bool, error) { for { var answer string fmt.Println(question) if _, err := fmt.Scan(&answer); err != nil { - log.Fatalln(err) + return false, err } else if parsed, value := stringToBool(answer); parsed { fmt.Println("") - return value + return value, nil } } panic("unreachable") @@ -352,7 +348,9 @@ func stringToBool(val string) (bool, bool) { return false, false } -func initConfigFile(context *cli.Context) { +func initConfigFile(context *cli.Context) error { + var err error + var localEnable bool if runtime.GOOS == "windows" { // Remove this if block when Privet support is added to Windows. @@ -362,23 +360,27 @@ func initConfigFile(context *cli.Context) { } else { fmt.Println("\"Local printing\" means that clients print directly to the connector via") fmt.Println("local subnet, and that an Internet connection is neither necessary nor used.") - localEnable = scanYesOrNo("Enable local printing?") + localEnable, err = scanYesOrNo("Enable local printing?") + if err != nil { + return err + } } var cloudEnable bool if runtime.GOOS == "windows" { // Remove this if block when Privet support is added to Windows. cloudEnable = true + } else if localEnable == false { + cloudEnable = true } else if context.IsSet("cloud-printing-enable") { cloudEnable = context.Bool("cloud-printing-enable") } else { fmt.Println("\"Cloud printing\" means that clients can print from anywhere on the Internet,") fmt.Println("and that printers must be explicitly shared with users.") - cloudEnable = scanYesOrNo("Enable cloud printing?") - } - - if !localEnable && !cloudEnable { - log.Fatalln("Try again. Either local or cloud (or both) must be enabled for the connector to do something.") + cloudEnable, err = scanYesOrNo("Enable cloud printing?") + if err != nil { + return err + } } var config *lib.Config @@ -387,14 +389,22 @@ func initConfigFile(context *cli.Context) { if cloudEnable { if context.IsSet("share-scope") { shareScope = context.String("share-scope") - } else if scanYesOrNo("Retain the user OAuth token to enable automatic sharing?") { - shareScope = scanNonEmptyString("User or group email address to share with:") + } else if yes, err := scanYesOrNo("Retain the user OAuth token to enable automatic sharing?"); err != nil { + return err + } else if yes { + shareScope, err = scanNonEmptyString("User or group email address to share with:") + if err != nil { + return err + } } if context.IsSet("proxy-name") { proxyName = context.String("proxy-name") } else { - proxyName = scanNonEmptyString("Proxy name for this connector:") + proxyName, err = scanNonEmptyString("Proxy name for this connector:") + if err != nil { + return err + } } var userClient *http.Client @@ -405,13 +415,19 @@ func initConfigFile(context *cli.Context) { } } else { var urt string - userClient, urt = getUserClientFromUser(context) + userClient, urt, err = getUserClientFromUser(context) + if err != nil { + return err + } if shareScope != "" { userRefreshToken = urt } } - xmppJID, robotRefreshToken = createRobotAccount(context, userClient) + xmppJID, robotRefreshToken, err = createRobotAccount(context, userClient) + if err != nil { + return err + } fmt.Println("Acquired OAuth credentials for robot account") fmt.Println("") @@ -421,7 +437,10 @@ func initConfigFile(context *cli.Context) { config = createLocalConfig(context) } - configFilename := writeConfigFile(context, config) + configFilename, err := config.Sparse(context).ToFile(context) + if err != nil { + return err + } fmt.Printf("The config file %s is ready to rock.\n", configFilename) if cloudEnable { fmt.Println("Keep it somewhere safe, as it contains an OAuth refresh token.") @@ -431,5 +450,8 @@ func initConfigFile(context *cli.Context) { if _, err := os.Stat(socketDirectory); os.IsNotExist(err) { fmt.Println("") fmt.Printf("When the connector runs, be sure the socket directory %s exists.\n", socketDirectory) + } else if err != nil { + return err } + return nil } diff --git a/gcp-connector-util/main_unix.go b/gcp-connector-util/main_unix.go index edd0498..708f9c5 100644 --- a/gcp-connector-util/main_unix.go +++ b/gcp-connector-util/main_unix.go @@ -9,7 +9,6 @@ package main import ( - "log" "os" "time" @@ -94,9 +93,6 @@ var unixCommands = []cli.Command{ } func main() { - // Suppress date/time prefix. - log.SetFlags(0) - app := cli.NewApp() app.Name = "gcp-connector-util" app.Usage = "Google Cloud Print Connector utility tools" diff --git a/gcp-connector-util/main_windows.go b/gcp-connector-util/main_windows.go index 60b212a..f93458c 100644 --- a/gcp-connector-util/main_windows.go +++ b/gcp-connector-util/main_windows.go @@ -10,7 +10,6 @@ package main import ( "fmt" - "log" "os" "path/filepath" @@ -61,35 +60,33 @@ var windowsCommands = []cli.Command{ }, } -func installEventLog(c *cli.Context) { +func installEventLog(c *cli.Context) error { err := eventlog.InstallAsEventCreate(lib.ConnectorName, eventlog.Error|eventlog.Warning|eventlog.Info) if err != nil { - fmt.Printf("Failed to install event log registry entries: %s\n", err) - os.Exit(1) + return fmt.Errorf("Failed to install event log registry entries: %s", err) } fmt.Println("Event log registry entries installed successfully") + return nil } -func removeEventLog(c *cli.Context) { +func removeEventLog(c *cli.Context) error { err := eventlog.Remove(lib.ConnectorName) if err != nil { - fmt.Printf("Failed to remove event log registry entries: %s\n", err) - os.Exit(1) + return fmt.Errorf("Failed to remove event log registry entries: %s\n", err) } fmt.Println("Event log registry entries removed successfully") + return nil } -func createService(c *cli.Context) { +func createService(c *cli.Context) error { exePath, err := filepath.Abs("gcp-windows-connector.exe") if err != nil { - fmt.Printf("Failed to find the connector executable: %s\n", err) - os.Exit(1) + return fmt.Errorf("Failed to find the connector executable: %s\n", err) } m, err := mgr.Connect() if err != nil { - fmt.Printf("Failed to connect to service control manager: %s\n", err) - os.Exit(1) + return fmt.Errorf("Failed to connect to service control manager: %s\n", err) } defer m.Disconnect() @@ -101,90 +98,81 @@ func createService(c *cli.Context) { } service, err := m.CreateService(lib.ConnectorName, exePath, config) if err != nil { - fmt.Printf("Failed to create service: %s\n", err) - os.Exit(1) + return fmt.Errorf("Failed to create service: %s\n", err) } defer service.Close() fmt.Println("Service created successfully") + return nil } -func deleteService(c *cli.Context) { +func deleteService(c *cli.Context) error { m, err := mgr.Connect() if err != nil { - fmt.Printf("Failed to connect to service control manager: %s\n", err) - os.Exit(1) + return fmt.Errorf("Failed to connect to service control manager: %s\n", err) } defer m.Disconnect() service, err := m.OpenService(lib.ConnectorName) if err != nil { - fmt.Printf("Failed to open service: %s\n", err) - os.Exit(1) + return fmt.Errorf("Failed to open service: %s\n", err) } defer service.Close() err = service.Delete() if err != nil { - fmt.Printf("Failed to delete service: %s\n", err) - os.Exit(1) + return fmt.Errorf("Failed to delete service: %s\n", err) } fmt.Println("Service deleted successfully") + return nil } -func startService(c *cli.Context) { +func startService(c *cli.Context) error { m, err := mgr.Connect() if err != nil { - fmt.Printf("Failed to connect to service control manager: %s\n", err) - os.Exit(1) + return fmt.Errorf("Failed to connect to service control manager: %s\n", err) } defer m.Disconnect() service, err := m.OpenService(lib.ConnectorName) if err != nil { - fmt.Printf("Failed to open service: %s\n", err) - os.Exit(1) + return fmt.Errorf("Failed to open service: %s\n", err) } defer service.Close() err = service.Start() if err != nil { - fmt.Printf("Failed to start service: %s\n", err) - os.Exit(1) + return fmt.Errorf("Failed to start service: %s\n", err) } fmt.Println("Service started successfully") + return nil } -func stopService(c *cli.Context) { +func stopService(c *cli.Context) error { m, err := mgr.Connect() if err != nil { - fmt.Printf("Failed to connect to service control manager: %s\n", err) - os.Exit(1) + return fmt.Errorf("Failed to connect to service control manager: %s\n", err) } defer m.Disconnect() service, err := m.OpenService(lib.ConnectorName) if err != nil { - fmt.Printf("Failed to open service: %s\n", err) - os.Exit(1) + return fmt.Errorf("Failed to open service: %s\n", err) } defer service.Close() _, err = service.Control(svc.Stop) if err != nil { - fmt.Printf("Failed to stop service: %s\n", err) - os.Exit(1) + return fmt.Errorf("Failed to stop service: %s\n", err) } fmt.Printf("Service stopped successfully") + return nil } func main() { - // Suppress date/time prefix. - log.SetFlags(0) - app := cli.NewApp() app.Name = "gcp-windows-connector-util" app.Usage = lib.ConnectorName + " for Windows utility tools" diff --git a/gcp-connector-util/monitor.go b/gcp-connector-util/monitor.go index b333d20..c54658c 100644 --- a/gcp-connector-util/monitor.go +++ b/gcp-connector-util/monitor.go @@ -11,7 +11,6 @@ package main import ( "fmt" "io/ioutil" - "log" "net" "os" "time" @@ -20,10 +19,10 @@ import ( "github.com/urfave/cli" ) -func monitorConnector(context *cli.Context) { +func monitorConnector(context *cli.Context) error { config, filename, err := lib.GetConfig(context) if err != nil { - log.Fatalf("Failed to read config file: %s\n", err) + return fmt.Errorf("Failed to read config file: %s", err) } if filename == "" { fmt.Println("No config file was found, so using defaults") @@ -31,31 +30,33 @@ func monitorConnector(context *cli.Context) { if _, err := os.Stat(config.MonitorSocketFilename); err != nil { if !os.IsNotExist(err) { - log.Fatalln(err) + return err } - log.Fatalf( - "No connector is running, or the monitoring socket %s is mis-configured\n", + return fmt.Errorf( + "No connector is running, or the monitoring socket %s is mis-configured", config.MonitorSocketFilename) } timer := time.AfterFunc(context.Duration("monitor-timeout"), func() { - log.Fatalf("Timeout after %s\n", context.Duration("monitor-timeout").String()) + fmt.Fprintf(os.Stderr, "Monitor check timed out after %s", context.Duration("monitor-timeout").String()) + os.Exit(1) }) conn, err := net.DialTimeout("unix", config.MonitorSocketFilename, time.Second) if err != nil { - log.Fatalf( - "No connector is running, or it is not listening to socket %s\n", + return fmt.Errorf( + "No connector is running, or it is not listening to socket %s", config.MonitorSocketFilename) } defer conn.Close() buf, err := ioutil.ReadAll(conn) if err != nil { - log.Fatalln(err) + return err } timer.Stop() fmt.Printf(string(buf)) + return nil } diff --git a/gcp-cups-connector/gcp-cups-connector.go b/gcp-cups-connector/gcp-cups-connector.go index 56fcf8c..e33dc69 100644 --- a/gcp-cups-connector/gcp-cups-connector.go +++ b/gcp-cups-connector/gcp-cups-connector.go @@ -205,10 +205,10 @@ func connector(context *cli.Context) error { if config.CloudPrintingEnable { if config.LocalPrintingEnable { log.Infof("Ready to rock as proxy '%s' and in local mode", config.ProxyName) - fmt.Println("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.Println("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") From 7afe5a472020257e19cbde9c795824205578f433 Mon Sep 17 00:00:00 2001 From: Jay Lee Date: Tue, 30 Aug 2016 20:44:41 +0000 Subject: [PATCH 35/76] log when fetch sees zero jobs (#298) log when fetch sees zero jobs --- gcp/gcp.go | 1 + 1 file changed, 1 insertion(+) diff --git a/gcp/gcp.go b/gcp/gcp.go index 011adca..42ca070 100644 --- a/gcp/gcp.go +++ b/gcp/gcp.go @@ -147,6 +147,7 @@ func (gcp *GoogleCloudPrint) Fetch(gcpID string) ([]Job, error) { responseBody, errorCode, _, err := postWithRetry(gcp.robotClient, gcp.baseURL+"fetch", form) if err != nil { if errorCode == 413 { + log.Debugf("No jobs returned by fetch (413 error)") // 413 means "Zero print jobs returned", which isn't really an error. return []Job{}, nil } From 5701480c972b5d651422558ed6148631c09daaf8 Mon Sep 17 00:00:00 2001 From: Nick Westgate Date: Fri, 2 Sep 2016 04:46:02 +1200 Subject: [PATCH 36/76] Windows installer dependencies.wxs sorted fixes #297 (#303) - Fixed Windows dependency filtering with case insensitive grep - Sorted dependencies with sort --- wix/dependencies.wxs | 90 ++++++++++++++++++------------------ wix/generate-dependencies.sh | 2 +- 2 files changed, 46 insertions(+), 46 deletions(-) diff --git a/wix/dependencies.wxs b/wix/dependencies.wxs index 1829225..9c1c8b6 100644 --- a/wix/dependencies.wxs +++ b/wix/dependencies.wxs @@ -5,20 +5,23 @@ Id="Dependencies" Directory="INSTALLFOLDER" Source="!(wix.DependencyDir)"> + + + - + - + - + - + @@ -27,49 +30,43 @@ - - - - + - + - + - + - + - + - + - + - + - + - - - - + - + @@ -78,73 +75,76 @@ - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + + + + diff --git a/wix/generate-dependencies.sh b/wix/generate-dependencies.sh index 1939ebc..a22bdd4 100644 --- a/wix/generate-dependencies.sh +++ b/wix/generate-dependencies.sh @@ -6,7 +6,7 @@ echo ' Id="Dependencies" Directory="INSTALLFOLDER" Source="!(wix.DependencyDir)">'>>dependencies.wxs -for f in `ldd ${GOPATH}/bin/gcp-windows-connector.exe | grep -v Windows | sed s/" =>.*"// | sed s/"\t"//` +for f in `ldd ${GOPATH}/bin/gcp-windows-connector.exe | grep -i -v Windows | sed s/" =>.*"// | sed s/"\t"// | sort` do echo " ">>dependencies.wxs; done From 7818031ef0b650b09302012a1df6d30644ff83b5 Mon Sep 17 00:00:00 2001 From: Nick Westgate Date: Wed, 7 Sep 2016 04:00:24 +1200 Subject: [PATCH 37/76] Windows Connector util name fixes #296 (#302) * Windows Connector util name fixes #296 Changed app name to gcp-connector-util * Related CLI tidy-ups - Ensure the new connector name ("Google Cloud Print Connector") is used everywhere - Ensure consistent usage of "for CUPS" and "for Windows" - Corrected the hard-coded default string for config-filename string which duplicated the text auto-genegerated by the cli package - Removed periods from unshare-gcp-printer and update-gcp-printer usage, and from a few other flag usage strings for consistency - Removed unnecessary apostrophe in show-gcp-printer-status usage --- gcp-connector-util/gcp-cups-connector-util.go | 26 +++++++++---------- gcp-connector-util/main_unix.go | 2 +- gcp-connector-util/main_windows.go | 2 +- gcp-cups-connector/gcp-cups-connector.go | 2 +- .../gcp-windows-connector.go | 2 +- lib/config.go | 5 ++-- 6 files changed, 19 insertions(+), 20 deletions(-) diff --git a/gcp-connector-util/gcp-cups-connector-util.go b/gcp-connector-util/gcp-cups-connector-util.go index 4b713f5..f5a76ba 100644 --- a/gcp-connector-util/gcp-cups-connector-util.go +++ b/gcp-connector-util/gcp-cups-connector-util.go @@ -80,7 +80,7 @@ var commonCommands = []cli.Command{ }, cli.Command{ Name: "show-gcp-printer-status", - Usage: "Shows the current status of a printer and it's jobs", + Usage: "Shows the current status of a printer and its jobs", Action: showGCPPrinterStatus, Flags: []cli.Flag{ cli.StringFlag{ @@ -95,16 +95,16 @@ var commonCommands = []cli.Command{ Flags: []cli.Flag{ cli.StringFlag{ Name: "printer-id", - Usage: "Printer to share.", + Usage: "Printer to share", }, cli.StringFlag{ Name: "email", - Usage: "Group or user to share with.", + Usage: "Group or user to share with", }, cli.StringFlag{ Name: "role", Value: "USER", - Usage: "Role granted. user or manager.", + Usage: "Role granted. user or manager", }, cli.BoolTFlag{ Name: "skip-notification", @@ -118,43 +118,43 @@ var commonCommands = []cli.Command{ }, cli.Command{ Name: "unshare-gcp-printer", - Usage: "Removes user or group access to printer.", + Usage: "Removes user or group access to printer", Action: unshareGCPPrinter, Flags: []cli.Flag{ cli.StringFlag{ Name: "printer-id", - Usage: "Printer to unshare.", + Usage: "Printer to unshare", }, cli.StringFlag{ Name: "email", - Usage: "Group or user to remove.", + Usage: "Group or user to remove", }, cli.BoolFlag{ Name: "public", - Usage: "Remove public printer access.", + Usage: "Remove public printer access", }, }, }, cli.Command{ Name: "update-gcp-printer", - Usage: "Modifies settings for a printer.", + Usage: "Modifies settings for a printer", Action: updateGCPPrinter, Flags: []cli.Flag{ cli.StringFlag{ Name: "printer-id", - Usage: "Printer to update.", + Usage: "Printer to update", }, cli.BoolFlag{ Name: "enable-quota", - Usage: "Set a daily per-user quota.", + Usage: "Set a daily per-user quota", }, cli.BoolFlag{ Name: "disable-quota", - Usage: "Disable daily per-user quota.", + Usage: "Disable daily per-user quota", }, cli.IntFlag{ Name: "daily-quota", - Usage: "Pages per-user per-day.", + Usage: "Pages per-user per-day", }, }, }, diff --git a/gcp-connector-util/main_unix.go b/gcp-connector-util/main_unix.go index 708f9c5..1c21f3f 100644 --- a/gcp-connector-util/main_unix.go +++ b/gcp-connector-util/main_unix.go @@ -95,7 +95,7 @@ var unixCommands = []cli.Command{ func main() { app := cli.NewApp() app.Name = "gcp-connector-util" - app.Usage = "Google Cloud Print Connector utility tools" + app.Usage = lib.ConnectorName + " for CUPS utility tools" app.Version = lib.BuildDate app.Flags = []cli.Flag{ lib.ConfigFilenameFlag, diff --git a/gcp-connector-util/main_windows.go b/gcp-connector-util/main_windows.go index f93458c..d1cca0f 100644 --- a/gcp-connector-util/main_windows.go +++ b/gcp-connector-util/main_windows.go @@ -174,7 +174,7 @@ func stopService(c *cli.Context) error { func main() { app := cli.NewApp() - app.Name = "gcp-windows-connector-util" + app.Name = "gcp-connector-util" app.Usage = lib.ConnectorName + " for Windows utility tools" app.Version = lib.BuildDate app.Flags = []cli.Flag{ diff --git a/gcp-cups-connector/gcp-cups-connector.go b/gcp-cups-connector/gcp-cups-connector.go index e33dc69..91532e3 100644 --- a/gcp-cups-connector/gcp-cups-connector.go +++ b/gcp-cups-connector/gcp-cups-connector.go @@ -34,7 +34,7 @@ import ( func main() { app := cli.NewApp() app.Name = "gcp-cups-connector" - app.Usage = "Google Cloud Print Connector for CUPS" + app.Usage = lib.ConnectorName + " for CUPS" app.Version = lib.BuildDate app.Flags = []cli.Flag{ lib.ConfigFilenameFlag, diff --git a/gcp-windows-connector/gcp-windows-connector.go b/gcp-windows-connector/gcp-windows-connector.go index 3aee9b2..9b225bf 100644 --- a/gcp-windows-connector/gcp-windows-connector.go +++ b/gcp-windows-connector/gcp-windows-connector.go @@ -28,7 +28,7 @@ import ( func main() { app := cli.NewApp() app.Name = "gcp-windows-connector" - app.Usage = "Google Cloud Print Connector for Windows" + app.Usage = lib.ConnectorName + " for Windows" app.Version = lib.BuildDate app.Flags = []cli.Flag{ lib.ConfigFilenameFlag, diff --git a/lib/config.go b/lib/config.go index 5d024fa..2d93906 100644 --- a/lib/config.go +++ b/lib/config.go @@ -10,7 +10,6 @@ package lib import ( "encoding/json" - "fmt" "io/ioutil" "reflect" "runtime" @@ -19,7 +18,7 @@ import ( ) const ( - ConnectorName = "Cloud Print Connector" + ConnectorName = "Google Cloud Print Connector" // A website with user-friendly information. ConnectorHomeURL = "https://github.com/google/cloud-print-connector" @@ -30,7 +29,7 @@ const ( var ( ConfigFilenameFlag = cli.StringFlag{ Name: "config-filename", - Usage: fmt.Sprintf("Connector config filename (default \"%s\")", defaultConfigFilename), + Usage: "Connector config filename", Value: defaultConfigFilename, } From 89eea6fb280a39115464d8ff991c82f3fb00c34f Mon Sep 17 00:00:00 2001 From: Jay Lee Date: Mon, 19 Sep 2016 09:21:27 -0400 Subject: [PATCH 38/76] Implement Privet on Windows fixes #246 First shot at Privet on Windows for local printing. Code needs a lot of work but on my test machine, local printing seems to work for basic scenarios. This patch uses github.com/andrewtj/dnssd for mDNS which in turn relies on Apple's Bonjour Service for Windows which must be installed: https://support.apple.com/kb/dl999?locale=en_US Apple does allow Bonjour Service to be distrubuted in some cases so we may be able to wrap it into the MSI installer but for now it will need to be installed before cloud-print-connector. --- gcp-connector-util/init.go | 5 +- .../gcp-windows-connector.go | 23 +++-- privet/windows.go | 87 ++++++++++++++++++- 3 files changed, 102 insertions(+), 13 deletions(-) diff --git a/gcp-connector-util/init.go b/gcp-connector-util/init.go index 0e053e1..c4435ea 100644 --- a/gcp-connector-util/init.go +++ b/gcp-connector-util/init.go @@ -352,10 +352,7 @@ func initConfigFile(context *cli.Context) error { var err error var localEnable bool - if runtime.GOOS == "windows" { - // Remove this if block when Privet support is added to Windows. - localEnable = false - } else if context.IsSet("local-printing-enable") { + if context.IsSet("local-printing-enable") { localEnable = context.Bool("local-printing-enable") } else { fmt.Println("\"Local printing\" means that clients print directly to the connector via") diff --git a/gcp-windows-connector/gcp-windows-connector.go b/gcp-windows-connector/gcp-windows-connector.go index 9b225bf..85b197e 100644 --- a/gcp-windows-connector/gcp-windows-connector.go +++ b/gcp-windows-connector/gcp-windows-connector.go @@ -14,13 +14,14 @@ import ( "os" "time" - "github.com/urfave/cli" "github.com/google/cloud-print-connector/gcp" "github.com/google/cloud-print-connector/lib" "github.com/google/cloud-print-connector/log" "github.com/google/cloud-print-connector/manager" + "github.com/google/cloud-print-connector/privet" "github.com/google/cloud-print-connector/winspool" "github.com/google/cloud-print-connector/xmpp" + "github.com/urfave/cli" "golang.org/x/sys/windows/svc" "golang.org/x/sys/windows/svc/debug" ) @@ -108,9 +109,6 @@ func (service *service) Execute(args []string, r <-chan svc.ChangeRequest, s cha if !config.CloudPrintingEnable && !config.LocalPrintingEnable { log.Fatal("Cannot run connector with both local_printing_enable and cloud_printing_enable set to false") return false, 1 - } else if config.LocalPrintingEnable { - log.Fatal("Local printing has not been implemented in this version of the Windows connector.") - return false, 1 } jobs := make(chan *lib.Job, 10) @@ -154,12 +152,27 @@ func (service *service) Execute(args []string, r <-chan svc.ChangeRequest, s cha return false, 1 } + var priv *privet.Privet + if config.LocalPrintingEnable { + log.Info("Starting local printing...") + if g == nil { + priv, err = privet.NewPrivet(jobs, config.LocalPortLow, config.LocalPortHigh, config.GCPBaseURL, nil) + } else { + priv, err = privet.NewPrivet(jobs, config.LocalPortLow, config.LocalPortHigh, config.GCPBaseURL, g.ProximityToken) + } + if err != nil { + log.Fatal(err) + return false, 1 + } + defer priv.Quit() + } + nativePrinterPollInterval, err := time.ParseDuration(config.NativePrinterPollInterval) if err != nil { log.Fatalf("Failed to parse printer poll interval: %s", err) return false, 1 } - pm, err := manager.NewPrinterManager(ws, g, nil, nativePrinterPollInterval, + pm, err := manager.NewPrinterManager(ws, g, priv, nativePrinterPollInterval, config.NativeJobQueueSize, *config.CUPSJobFullUsername, config.ShareScope, jobs, xmppNotifications) if err != nil { log.Fatal(err) diff --git a/privet/windows.go b/privet/windows.go index c9f9242..fe7ca73 100644 --- a/privet/windows.go +++ b/privet/windows.go @@ -9,25 +9,104 @@ package privet import ( - "errors" + "fmt" + "sync" + "github.com/andrewtj/dnssd" + "github.com/google/cloud-print-connector/log" ) -type zeroconf struct{} +const serviceType = "_privet._tcp" + +type zeroconf struct { + printers map[string]dnssd.RegisterOp + pMutex sync.RWMutex // Protects printers. +} func newZeroconf() (*zeroconf, error) { - return nil, errors.New("Privet has not been implemented for Windows") + z := zeroconf{ + printers: make(map[string]dnssd.RegisterOp), + } + return &z, nil } +func nullDNSSDRegisterCallback(_ *dnssd.RegisterOp, _ error, _ bool, _, _, _ string) {} + func (z *zeroconf) addPrinter(name string, port uint16, ty, note, url, id string, online bool) error { + z.pMutex.RLock() + if _, exists := z.printers[name]; exists { + z.pMutex.RUnlock() + return fmt.Errorf("Bonjour already has printer %s", name) + } + z.pMutex.RUnlock() + + op := dnssd.NewRegisterOp(name, serviceType, int(port), nullDNSSDRegisterCallback) + op.SetTXTPair("ty", ty) + op.SetTXTPair("note", note) + op.SetTXTPair("url", url) + op.SetTXTPair("type", "printer") + op.SetTXTPair("id", id) + var cs string + if online { + cs = "online" + } else { + cs = "offline" + } + op.SetTXTPair("cs", cs) + err := op.Start() + if err != nil { + return err + } + z.pMutex.Lock() + defer z.pMutex.Unlock() + + z.printers[name] = *op return nil } func (z *zeroconf) updatePrinterTXT(name, ty, note, url, id string, online bool) error { + z.pMutex.RLock() + defer z.pMutex.RUnlock() + + if op, exists := z.printers[name]; exists { + op.SetTXTPair("ty", ty) + op.SetTXTPair("note", note) + op.SetTXTPair("url", url) + op.SetTXTPair("type", "printer") + op.SetTXTPair("id", id) + var cs string + if online { + cs = "online" + } else { + cs = "offline" + } + op.SetTXTPair("cs", cs) + } else { + return fmt.Errorf("Bonjour can't update printer %s that hasn't been added", name) + } return nil } func (z *zeroconf) removePrinter(name string) error { + z.pMutex.RLock() + defer z.pMutex.RUnlock() + + if op, exists := z.printers[name]; exists { + op.Stop() + delete(z.printers, name) + log.Info("Unregistered %s with bonjour", name) + } else { + return fmt.Errorf("Bonjour can't remove printer %s that hasn't been added", name) + } + return nil } -func (z *zeroconf) quit() {} +func (z *zeroconf) quit() { + z.pMutex.Lock() + defer z.pMutex.Unlock() + + for name, op := range z.printers { + op.Stop() + delete(z.printers, name) + } +} \ No newline at end of file From 6c536203e3f7efa94ffa5ce9209d260a679ae3ba Mon Sep 17 00:00:00 2001 From: Jay Lee Date: Mon, 19 Sep 2016 09:27:32 -0400 Subject: [PATCH 39/76] Revert "Implement Privet on Windows fixes #246" This reverts commit 89eea6fb280a39115464d8ff991c82f3fb00c34f. --- gcp-connector-util/init.go | 5 +- .../gcp-windows-connector.go | 23 ++--- privet/windows.go | 87 +------------------ 3 files changed, 13 insertions(+), 102 deletions(-) diff --git a/gcp-connector-util/init.go b/gcp-connector-util/init.go index c4435ea..0e053e1 100644 --- a/gcp-connector-util/init.go +++ b/gcp-connector-util/init.go @@ -352,7 +352,10 @@ func initConfigFile(context *cli.Context) error { var err error var localEnable bool - if context.IsSet("local-printing-enable") { + if runtime.GOOS == "windows" { + // Remove this if block when Privet support is added to Windows. + localEnable = false + } else if context.IsSet("local-printing-enable") { localEnable = context.Bool("local-printing-enable") } else { fmt.Println("\"Local printing\" means that clients print directly to the connector via") diff --git a/gcp-windows-connector/gcp-windows-connector.go b/gcp-windows-connector/gcp-windows-connector.go index 85b197e..9b225bf 100644 --- a/gcp-windows-connector/gcp-windows-connector.go +++ b/gcp-windows-connector/gcp-windows-connector.go @@ -14,14 +14,13 @@ import ( "os" "time" + "github.com/urfave/cli" "github.com/google/cloud-print-connector/gcp" "github.com/google/cloud-print-connector/lib" "github.com/google/cloud-print-connector/log" "github.com/google/cloud-print-connector/manager" - "github.com/google/cloud-print-connector/privet" "github.com/google/cloud-print-connector/winspool" "github.com/google/cloud-print-connector/xmpp" - "github.com/urfave/cli" "golang.org/x/sys/windows/svc" "golang.org/x/sys/windows/svc/debug" ) @@ -109,6 +108,9 @@ func (service *service) Execute(args []string, r <-chan svc.ChangeRequest, s cha if !config.CloudPrintingEnable && !config.LocalPrintingEnable { log.Fatal("Cannot run connector with both local_printing_enable and cloud_printing_enable set to false") return false, 1 + } else if config.LocalPrintingEnable { + log.Fatal("Local printing has not been implemented in this version of the Windows connector.") + return false, 1 } jobs := make(chan *lib.Job, 10) @@ -152,27 +154,12 @@ func (service *service) Execute(args []string, r <-chan svc.ChangeRequest, s cha return false, 1 } - var priv *privet.Privet - if config.LocalPrintingEnable { - log.Info("Starting local printing...") - if g == nil { - priv, err = privet.NewPrivet(jobs, config.LocalPortLow, config.LocalPortHigh, config.GCPBaseURL, nil) - } else { - priv, err = privet.NewPrivet(jobs, config.LocalPortLow, config.LocalPortHigh, config.GCPBaseURL, g.ProximityToken) - } - if err != nil { - log.Fatal(err) - return false, 1 - } - defer priv.Quit() - } - nativePrinterPollInterval, err := time.ParseDuration(config.NativePrinterPollInterval) if err != nil { log.Fatalf("Failed to parse printer poll interval: %s", err) return false, 1 } - pm, err := manager.NewPrinterManager(ws, g, priv, nativePrinterPollInterval, + pm, err := manager.NewPrinterManager(ws, g, nil, nativePrinterPollInterval, config.NativeJobQueueSize, *config.CUPSJobFullUsername, config.ShareScope, jobs, xmppNotifications) if err != nil { log.Fatal(err) diff --git a/privet/windows.go b/privet/windows.go index fe7ca73..c9f9242 100644 --- a/privet/windows.go +++ b/privet/windows.go @@ -9,104 +9,25 @@ package privet import ( - "fmt" - "sync" - "github.com/andrewtj/dnssd" - "github.com/google/cloud-print-connector/log" + "errors" ) -const serviceType = "_privet._tcp" - -type zeroconf struct { - printers map[string]dnssd.RegisterOp - pMutex sync.RWMutex // Protects printers. -} +type zeroconf struct{} func newZeroconf() (*zeroconf, error) { - z := zeroconf{ - printers: make(map[string]dnssd.RegisterOp), - } - return &z, nil + return nil, errors.New("Privet has not been implemented for Windows") } -func nullDNSSDRegisterCallback(_ *dnssd.RegisterOp, _ error, _ bool, _, _, _ string) {} - func (z *zeroconf) addPrinter(name string, port uint16, ty, note, url, id string, online bool) error { - z.pMutex.RLock() - if _, exists := z.printers[name]; exists { - z.pMutex.RUnlock() - return fmt.Errorf("Bonjour already has printer %s", name) - } - z.pMutex.RUnlock() - - op := dnssd.NewRegisterOp(name, serviceType, int(port), nullDNSSDRegisterCallback) - op.SetTXTPair("ty", ty) - op.SetTXTPair("note", note) - op.SetTXTPair("url", url) - op.SetTXTPair("type", "printer") - op.SetTXTPair("id", id) - var cs string - if online { - cs = "online" - } else { - cs = "offline" - } - op.SetTXTPair("cs", cs) - err := op.Start() - if err != nil { - return err - } - z.pMutex.Lock() - defer z.pMutex.Unlock() - - z.printers[name] = *op return nil } func (z *zeroconf) updatePrinterTXT(name, ty, note, url, id string, online bool) error { - z.pMutex.RLock() - defer z.pMutex.RUnlock() - - if op, exists := z.printers[name]; exists { - op.SetTXTPair("ty", ty) - op.SetTXTPair("note", note) - op.SetTXTPair("url", url) - op.SetTXTPair("type", "printer") - op.SetTXTPair("id", id) - var cs string - if online { - cs = "online" - } else { - cs = "offline" - } - op.SetTXTPair("cs", cs) - } else { - return fmt.Errorf("Bonjour can't update printer %s that hasn't been added", name) - } return nil } func (z *zeroconf) removePrinter(name string) error { - z.pMutex.RLock() - defer z.pMutex.RUnlock() - - if op, exists := z.printers[name]; exists { - op.Stop() - delete(z.printers, name) - log.Info("Unregistered %s with bonjour", name) - } else { - return fmt.Errorf("Bonjour can't remove printer %s that hasn't been added", name) - } - return nil } -func (z *zeroconf) quit() { - z.pMutex.Lock() - defer z.pMutex.Unlock() - - for name, op := range z.printers { - op.Stop() - delete(z.printers, name) - } -} \ No newline at end of file +func (z *zeroconf) quit() {} From 79f973a16d4d787d4f1054281be714b7c089626d Mon Sep 17 00:00:00 2001 From: Jay Lee Date: Mon, 19 Sep 2016 13:44:58 -0400 Subject: [PATCH 40/76] Generate a v4 UUID for proxy name instead of asking user Don't ask the user to enter a name for the proxy since the name is not significant in most cases and may confuse user. If needed, user can still set proxy name by running: gcp-connector-util init --proxy-name mypreferred-name --- gcp-connector-util/init.go | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/gcp-connector-util/init.go b/gcp-connector-util/init.go index 0e053e1..2f22c22 100644 --- a/gcp-connector-util/init.go +++ b/gcp-connector-util/init.go @@ -22,6 +22,7 @@ import ( "github.com/google/cloud-print-connector/gcp" "github.com/google/cloud-print-connector/lib" "github.com/urfave/cli" + "github.com/satori/go.uuid" "golang.org/x/oauth2" ) @@ -401,10 +402,7 @@ func initConfigFile(context *cli.Context) error { if context.IsSet("proxy-name") { proxyName = context.String("proxy-name") } else { - proxyName, err = scanNonEmptyString("Proxy name for this connector:") - if err != nil { - return err - } + proxyName = uuid.NewV4().String() } var userClient *http.Client From 6985e8f5591ea09914c9da2143b5995bd1eba7ab Mon Sep 17 00:00:00 2001 From: Jay Lee Date: Mon, 19 Sep 2016 14:48:38 -0400 Subject: [PATCH 41/76] go fmt cleaup --- gcp-connector-util/init.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gcp-connector-util/init.go b/gcp-connector-util/init.go index 2f22c22..c5fa303 100644 --- a/gcp-connector-util/init.go +++ b/gcp-connector-util/init.go @@ -21,8 +21,8 @@ import ( "github.com/google/cloud-print-connector/gcp" "github.com/google/cloud-print-connector/lib" + "github.com/satori/go.uuid" "github.com/urfave/cli" - "github.com/satori/go.uuid" "golang.org/x/oauth2" ) From d3cc60ceb6a94324af615639f29667bd72960b62 Mon Sep 17 00:00:00 2001 From: Jay Lee Date: Wed, 21 Sep 2016 16:07:44 -0400 Subject: [PATCH 42/76] init: combine sharing prompts and move after authentication Fixes #309 --- gcp-connector-util/init.go | 58 +++++++++++++++++++------------------- 1 file changed, 29 insertions(+), 29 deletions(-) diff --git a/gcp-connector-util/init.go b/gcp-connector-util/init.go index c5fa303..e8b539c 100644 --- a/gcp-connector-util/init.go +++ b/gcp-connector-util/init.go @@ -9,6 +9,7 @@ https://developers.google.com/open-source/licenses/bsd package main import ( + "bufio" "encoding/json" "fmt" "net/http" @@ -305,18 +306,16 @@ func createRobotAccount(context *cli.Context, userClient *http.Client) (string, return xmppJID, token, nil } -func scanNonEmptyString(prompt string) (string, error) { - for { - var answer string - fmt.Println(prompt) - if length, err := fmt.Scan(&answer); err != nil { - return "", err - } else if length > 0 { - fmt.Println("") - return answer, nil - } +func scanString(prompt string) (string, error) { + fmt.Println(prompt) + reader := bufio.NewReader(os.Stdin) + if answer, err := reader.ReadString('\n'); err != nil { + return "", err + } else { + answer = answer[:len(answer)-1] // remove newline + fmt.Println("") + return answer, nil } - panic("unreachable") } func scanYesOrNo(question string) (bool, error) { @@ -388,17 +387,6 @@ func initConfigFile(context *cli.Context) error { var xmppJID, robotRefreshToken, userRefreshToken, shareScope, proxyName string if cloudEnable { - if context.IsSet("share-scope") { - shareScope = context.String("share-scope") - } else if yes, err := scanYesOrNo("Retain the user OAuth token to enable automatic sharing?"); err != nil { - return err - } else if yes { - shareScope, err = scanNonEmptyString("User or group email address to share with:") - if err != nil { - return err - } - } - if context.IsSet("proxy-name") { proxyName = context.String("proxy-name") } else { @@ -406,20 +394,14 @@ func initConfigFile(context *cli.Context) error { } var userClient *http.Client + var urt string if context.IsSet("gcp-user-refresh-token") { userClient = getUserClientFromToken(context) - if shareScope != "" { - userRefreshToken = context.String("gcp-user-refresh-token") - } } else { - var urt string userClient, urt, err = getUserClientFromUser(context) if err != nil { return err } - if shareScope != "" { - userRefreshToken = urt - } } xmppJID, robotRefreshToken, err = createRobotAccount(context, userClient) @@ -429,6 +411,24 @@ func initConfigFile(context *cli.Context) error { fmt.Println("Acquired OAuth credentials for robot account") fmt.Println("") + + if context.IsSet("share-scope") { + shareScope = context.String("share-scope") + } else { + shareScope, err = scanString("Enter the email address of a user or group with whom all printers will automatically be shared or press enter to disable automatic sharing:") + if err != nil { + return err + } + } + + if shareScope != "" { + if context.IsSet("gcp-user-refresh-token") { + userRefreshToken = context.String("gcp-user-refresh-token") + } else { + userRefreshToken = urt + } + } + config = createCloudConfig(context, xmppJID, robotRefreshToken, userRefreshToken, shareScope, proxyName, localEnable) } else { From be52dbb2c7497bc53d2f76fd488aa887f2ccf25b Mon Sep 17 00:00:00 2001 From: Jay Lee Date: Thu, 22 Sep 2016 11:01:53 -0400 Subject: [PATCH 43/76] "leave blank" instead of "press enter" --- gcp-connector-util/init.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gcp-connector-util/init.go b/gcp-connector-util/init.go index e8b539c..fa7108c 100644 --- a/gcp-connector-util/init.go +++ b/gcp-connector-util/init.go @@ -415,7 +415,7 @@ func initConfigFile(context *cli.Context) error { if context.IsSet("share-scope") { shareScope = context.String("share-scope") } else { - shareScope, err = scanString("Enter the email address of a user or group with whom all printers will automatically be shared or press enter to disable automatic sharing:") + shareScope, err = scanString("Enter the email address of a user or group with whom all printers will automatically be shared or leave blank to disable automatic sharing:") if err != nil { return err } From 4a344809fb0d974339c71400f84310a63aa3445f Mon Sep 17 00:00:00 2001 From: Jay Lee Date: Thu, 29 Sep 2016 16:23:27 -0400 Subject: [PATCH 44/76] Improvements to WIX to allow automation and silent install (#314) * Improvements to WIX to allow automation and silent install - Change default version to 1.0.0 since upgrades do not consider 4th digit in determining upgrade (e.g. 1.0.0.1 not an upgrade to 1.0.0.0) - Depend on Tcpip driver for startup. Fixes #312 - Don't wait for service to start during MSI install. Allows installation to complete even with a bad / missing config JSON. - Add RUNINT parameter to allow disabling init during install. For example: msiexec /i windows-connector.msi RUNINIT=0 - Add INITPARAMS parameter to allow setting of parameters supplied to init during install. For example, to skip all prompts during init and create new robot: msiexec /qr /i windows-connector.msi INITPARAMS="--gcp-user-refresh-token --share-scope=" * implement suggested changes by @arastooz and @jacobemarble --- wix/windows-connector.wxs | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/wix/windows-connector.wxs b/wix/windows-connector.wxs index 3441d3c..089075a 100644 --- a/wix/windows-connector.wxs +++ b/wix/windows-connector.wxs @@ -4,7 +4,7 @@ Id="*" Name="Google Cloud Print Windows Connector" Language="1033" - Version="1.0.0.0" + Version="0.9.0" Manufacturer="Google, Inc." UpgradeCode="15C3FD61-B03C-4C04-A56D-CD8424C99D7F"> + + + + STARTSERVICE="YES" + @@ -130,7 +137,7 @@ - NOT CONFIGFILE and NOT Installed and NOT WIX_UPGRADE_DETECTED + NOT CONFIGFILE and NOT Installed and NOT WIX_UPGRADE_DETECTED and RUNINIT="YES" @@ -143,12 +150,16 @@ +