<commit_before>/*
Copyright 2014 Google Inc. All rights reserved.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

// Package gcp is the Google Cloud Print API client.
package gcp

import (
	"cups-connector/lib"
	"encoding/base64"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
	"net/url"
	"runtime"
	"strings"
	"time"

	"github.com/golang/glog"
	"golang.org/x/oauth2"
)

const (
	// All GCP API methods fall under here.
	baseURL = "https://www.google.com/cloudprint/"

	// XMPP connections fail. Attempt to reconnect a few times before giving up.
	restartXMPPMaxRetries = 4

	// Stop retrying when two failurees occur in succession in this quantity of seconds.
	minimumTimeBetweenXMPPFailures = 3

	// OAuth constants.
	ClientID        = "539833558011-35iq8btpgas80nrs3o7mv99hm95d4dv6.apps.googleusercontent.com"
	ClientSecret    = "<KEY>"
	RedirectURL     = "oob"
	ScopeCloudPrint = "https://www.googleapis.com/auth/cloudprint"
	ScopeGoogleTalk = "https://www.googleapis.com/auth/googletalk"
	AccessType      = "offline"
	AuthURL         = "https://accounts.google.com/o/oauth2/auth"
	TokenURL        = "https://accounts.google.com/o/oauth2/token"
	CreateRobotURL  = "https://www.google.com/cloudprint/createrobot"
)

// GoogleCloudPrint is the interface between Go and the Google Cloud Print API.
type GoogleCloudPrint struct {
	xmppJID     string
	xmppClient  *gcpXMPP
	robotClient *http.Client
	userClient  *http.Client
	proxyName   string
}

// NewGoogleCloudPrint establishes a connection with GCP, returns a new GoogleCloudPrint object.
func NewGoogleCloudPrint(xmppJID, robotRefreshToken, userRefreshToken, proxyName string) (*GoogleCloudPrint, error) {
	robotClient, err := newClient(robotRefreshToken, ScopeCloudPrint, ScopeGoogleTalk)
	if err != nil {
		return nil, err
	}

	var userClient *http.Client
	if userRefreshToken != "" {
		userClient, err = newClient(userRefreshToken, ScopeCloudPrint)
		if err != nil {
			return nil, err
		}
	}

	gcp := &GoogleCloudPrint{xmppJID, nil, robotClient, userClient, proxyName}
	if err = gcp.restartXMPP(); err != nil {
		return nil, err
	}
	glog.Info("Started XMPP successfully")

	return gcp, nil
}

type localSettingsInner struct {
	XMPPTimeoutValue uint32 `json:"xmpp_timeout_value"`
	// Other values can be set here; omit until needed.
}

type localSettingsOuter struct {
	// "current" name must be lower-case, but can't annotate inner struct
	// without separate type declaration.
	Current localSettingsInner `json:"current"`
}

// Turn arguments into a JSON-encoded GCP LocalSettings object.
func marshalLocalSettings(xmppTimeout uint32) (string, error) {
	var ls localSettingsOuter
	ls.Current.XMPPTimeoutValue = xmppTimeout
	lss, err := json.Marshal(ls)
	if err != nil {
		return "", err
	}
	return string(lss), nil
}

func newClient(refreshToken string, scopes ...string) (*http.Client, error) {
	config := &oauth2.Config{
		ClientID:     ClientID,
		ClientSecret: ClientSecret,
		Endpoint: oauth2.Endpoint{
			AuthURL:  AuthURL,
			TokenURL: TokenURL,
		},
		RedirectURL: RedirectURL,
		Scopes:      scopes,
	}

	token := &oauth2.Token{RefreshToken: refreshToken}
	client := config.Client(oauth2.NoContext, token)

	return client, nil
}

// Quit terminates the XMPP conversation so that new jobs stop arriving.
func (gcp *GoogleCloudPrint) Quit() {
	gcp.xmppClient.quit()
}

// CanShare answers the question "can we share printers when they are registered?"
func (gcp *GoogleCloudPrint) CanShare() bool {
	return gcp.userClient != nil
}

// restartXMPP tries to start an XMPP conversation multiple times, then panics.
func (gcp *GoogleCloudPrint) restartXMPP() error {
	if gcp.xmppClient != nil {
		go gcp.xmppClient.quit()
	}

	var err error
	for i := 0; i < restartXMPPMaxRetries; i++ {
		if err == nil {
			// The current access token is the XMPP password.
			token, err := gcp.robotClient.Transport.(*oauth2.Transport).Source.Token()
			if err != nil {
				return fmt.Errorf("Failed to restart XMPP because: %s", err)
			}

			var xmpp *gcpXMPP
			xmpp, err = newXMPP(gcp.xmppJID, token.AccessToken, gcp.proxyName)
			if err == nil {
				gcp.xmppClient = xmpp
				return nil
			}
		}

		// Sleep for 1, 2, 4, 8 seconds.
		time.Sleep(time.Duration((i+1)*2) * time.Second)
	}

	return fmt.Errorf("Failed to (re-)start XMPP conversation: %s", err)
}

// nextWaitingPrinter calls xmppClient.nextWaitingPrinter(), with retries on
// failure. If a call to this function fails, then there's probably no reason to
// go on living.
//
// Returns a base64-encoded string representation of the ID of the printer
// that has waiting job(s).
func (gcp *GoogleCloudPrint) nextWaitingPrinterWithRetries() (string, error) {
	lastFailure := time.Unix(0, 0)
	for {
		printerIDb64, err := gcp.xmppClient.nextWaitingPrinter()
		if err == nil {
			// Success!
			return printerIDb64, nil
		}

		if err == ErrClosed {
			// The connection is closed.
			return "", err<commit_msg>Fix XMPP restart bug (only tries once)<commit_after>/*
Copyright 2014 Google Inc. All rights reserved.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

// Package gcp is the Google Cloud Print API client.
package gcp

import (
	"cups-connector/lib"
	"encoding/base64"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
	"net/url"
	"runtime"
	"strings"
	"time"

	"github.com/golang/glog"
	"golang.org/x/oauth2"
)

const (
	// All GCP API methods fall under here.
	baseURL = "https://www.google.com/cloudprint/"

	// XMPP connections fail. Attempt to reconnect a few times before giving up.
	restartXMPPMaxRetries = 4

	// Stop retrying when two failurees occur in succession in this quantity of seconds.
	minimumTimeBetweenXMPPFailures = 3

	// OAuth constants.
	ClientID        = "539833558011-35iq8btpgas80nrs3o7mv99hm95d4dv6.apps.googleusercontent.com"
	ClientSecret    = "<KEY>"
	RedirectURL     = "oob"
	ScopeCloudPrint = "https://www.googleapis.com/auth/cloudprint"
	ScopeGoogleTalk = "https://www.googleapis.com/auth/googletalk"
	AccessType      = "offline"
	AuthURL         = "https://accounts.google.com/o/oauth2/auth"
	TokenURL        = "https://accounts.google.com/o/oauth2/token"
	CreateRobotURL  = "https://www.google.com/cloudprint/createrobot"
)

// GoogleCloudPrint is the interface between Go and the Google Cloud Print API.
type GoogleCloudPrint struct {
	xmppJID     string
	xmppClient  *gcpXMPP
	robotClient *http.Client
	userClient  *http.Client
	proxyName   string
}

// NewGoogleCloudPrint establishes a connection with GCP, returns a new GoogleCloudPrint object.
func NewGoogleCloudPrint(xmppJID, robotRefreshToken, userRefreshToken, proxyName string) (*GoogleCloudPrint, error) {
	robotClient, err := newClient(robotRefreshToken, ScopeCloudPrint, ScopeGoogleTalk)
	if err != nil {
		return nil, err
	}

	var userClient *http.Client
	if userRefreshToken != "" {
		userClient, err = newClient(userRefreshToken, ScopeCloudPrint)
		if err != nil {
			return nil, err
		}
	}

	gcp := &GoogleCloudPrint{xmppJID, nil, robotClient, userClient, proxyName}
	if err = gcp.restartXMPP(); err != nil {
		return nil, err
	}
	glog.Info("Started XMPP successfully")

	return gcp, nil
}

type localSettingsInner struct {
	XMPPTimeoutValue uint32 `json:"xmpp_timeout_value"`
	// Other values can be set here; omit until needed.
}

type localSettingsOuter struct {
	// "current" name must be lower-case, but can't annotate inner struct
	// without separate type declaration.
	Current localSettingsInner `json:"current"`
}

// Turn arguments into a JSON-encoded GCP LocalSettings object.
func marshalLocalSettings(xmppTimeout uint32) (string, error) {
	var ls localSettingsOuter
	ls.Current.XMPPTimeoutValue = xmppTimeout
	lss, err := json.Marshal(ls)
	if err != nil {
		return "", err
	}
	return string(lss), nil
}

func newClient(refreshToken string, scopes ...string) (*http.Client, error) {
	config := &oauth2.Config{
		ClientID:     ClientID,
		ClientSecret: ClientSecret,
		Endpoint: oauth2.Endpoint{
			AuthURL:  AuthURL,
			TokenURL: TokenURL,
		},
		RedirectURL: RedirectURL,
		Scopes:      scopes,
	}

	token := &oauth2.Token{RefreshToken: refreshToken}
	client := config.Client(oauth2.NoContext, token)

	return client, nil
}

// Quit terminates the XMPP conversation so that new jobs stop arriving.
func (gcp *GoogleCloudPrint) Quit() {
	gcp.xmppClient.quit()
}

// CanShare answers the question "can we share printers when they are registered?"
func (gcp *GoogleCloudPrint) CanShare() bool {
	return gcp.userClient != nil
}

// restartXMPP tries to start an XMPP conversation multiple times, then panics.
func (gcp *GoogleCloudPrint) restartXMPP() error {
	if gcp.xmppClient != nil {
		go gcp.xmppClient.quit()
	}

	var err error
	for i := 0; i < restartXMPPMaxRetries; i++ {
		// The current access token is the XMPP password.
		var token *oauth2.Token
		token, err = gcp.robotClient.Transport.(*oauth2.Transport).Source.Token()

		if err == nil {
			var xmpp *gcpXMPP
			xmpp, err = newXMPP(gcp.xmppJID, token.AccessToken, gcp.proxyName)

			if err == nil {
				gcp.xmppClient = xmpp
				return nil
			}
		}

		// Sleep for 1, 2, 4, 8 seconds.
		time.Sleep(time.Duration((i+1)*2) * time.Second)
	}

	return fmt.Errorf("Failed to (re-)start XMPP conversation: %s", err)
}

// nextWaitingPrinter calls xmppClient.nextWaitingPrinter(), with retries on
// failure. If a call to this function fails, then there's probably no reason to
// go on living.
//
// Returns a base64-encoded string representation of the ID of the printer
// that has waiting job(s).
func (gcp *GoogleCloudPrint) nextWaitingPrinterWithRetries() (string, error) {
	lastFailure := time.Unix(0, 0)
	for {
		printerIDb64, err := gcp.xmppClient.nextWaitingPrinter()
		if err == nil {
			// Success!
			return printerIDb64, nil
		}

		if err == ErrClosed {
			// The connection is closed.
			return "", err
{'fraction_non_alphanumeric': 0.1037578674564976, 'fraction_numerical': 0.011940022213994816, 'mean_word_length': 3.9135970895861756, 'pattern_counts': {'":': 0, '<': 7, '<?xml version=': 0, '>': 5, 'https://': 12, 'lorem ipsum': 0, 'www.': 10, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '850929', 'n_tokens_mistral': 3826, 'n_tokens_neox': 3563, 'n_words': 1091}