// Send sends a message. // If any failures occur with specific recipients, the error will be an appengine.MultiError. func (m *Message) Send(c appengine.Context) error { req := &pb.XmppMessageRequest{ Jid: m.To, Body: &m.Body, RawXml: &m.RawXML, } if m.Type != "" && m.Type != "chat" { req.Type = &m.Type } if m.Sender != "" { req.FromJid = &m.Sender } res := &pb.XmppMessageResponse{} if err := c.Call("xmpp", "SendMessage", req, res, nil); err != nil { return err } if len(res.Status) != len(req.Jid) { return fmt.Errorf("xmpp: sent message to %d JIDs, but only got %d statuses back", len(req.Jid), len(res.Status)) } me, any := make(appengine.MultiError, len(req.Jid)), false for i, st := range res.Status { if st != pb.XmppMessageResponse_NO_ERROR { me[i] = errors.New(st.String()) any = true } } if any { return me } return nil }
// DeleteServingURL deletes the serving URL for an image. func DeleteServingURL(c appengine.Context, key appengine.BlobKey) error { req := &pb.ImagesDeleteUrlBaseRequest{ BlobKey: (*string)(&key), } res := &pb.ImagesDeleteUrlBaseResponse{} return c.Call("images", "DeleteUrlBase", req, res, nil) }
// DeleteMulti deletes multiple tasks from a named queue. // If a given task could not be deleted, an appengine.MultiError is returned. func DeleteMulti(c appengine.Context, tasks []*Task, queueName string) error { taskNames := make([][]byte, len(tasks)) for i, t := range tasks { taskNames[i] = []byte(t.Name) } if queueName == "" { queueName = "default" } req := &pb.TaskQueueDeleteRequest{ QueueName: []byte(queueName), TaskName: taskNames, } res := &pb.TaskQueueDeleteResponse{} if err := c.Call("taskqueue", "Delete", req, res, nil); err != nil { return err } if a, b := len(req.TaskName), len(res.Result); a != b { return fmt.Errorf("taskqueue: internal error: requested deletion of %d tasks, got %d results", a, b) } me, any := make(appengine.MultiError, len(res.Result)), false for i, ec := range res.Result { if ec != pb.TaskQueueServiceError_OK { me[i] = &internal.APIError{ Service: "taskqueue", Code: int32(ec), } any = true } } if any { return me } return nil }
// DeleteMulti is a batch version of Delete. // If any keys cannot be found, an appengine.MultiError is returned. // Each key must be at most 250 bytes in length. func DeleteMulti(c appengine.Context, key []string) error { req := &pb.MemcacheDeleteRequest{ Item: make([]*pb.MemcacheDeleteRequest_Item, len(key)), } for i, k := range key { req.Item[i] = &pb.MemcacheDeleteRequest_Item{Key: []byte(k)} } res := &pb.MemcacheDeleteResponse{} if err := c.Call("memcache", "Delete", req, res, nil); err != nil { return err } if len(res.DeleteStatus) != len(key) { return ErrServerError } me, any := make(appengine.MultiError, len(key)), false for i, s := range res.DeleteStatus { switch s { case pb.MemcacheDeleteResponse_DELETED: // OK case pb.MemcacheDeleteResponse_NOT_FOUND: me[i] = ErrCacheMiss any = true default: me[i] = ErrServerError any = true } } if any { return me } return nil }
func lease(c appengine.Context, maxTasks int, queueName string, leaseTime int, groupByTag bool, tag []byte) ([]*Task, error) { if queueName == "" { queueName = "default" } req := &pb.TaskQueueQueryAndOwnTasksRequest{ QueueName: []byte(queueName), LeaseSeconds: proto.Float64(float64(leaseTime)), MaxTasks: proto.Int64(int64(maxTasks)), GroupByTag: proto.Bool(groupByTag), Tag: tag, } res := &pb.TaskQueueQueryAndOwnTasksResponse{} callOpts := &internal.CallOptions{ Timeout: 10 * time.Second, } if err := c.Call("taskqueue", "QueryAndOwnTasks", req, res, callOpts); err != nil { return nil, err } tasks := make([]*Task, len(res.Task)) for i, t := range res.Task { tasks[i] = &Task{ Payload: t.Body, Name: string(t.TaskName), Method: "PULL", ETA: time.Unix(0, *t.EtaUsec*1e3), RetryCount: *t.RetryCount, Tag: string(t.Tag), } } return tasks, nil }
// AllocateIDs returns a range of n integer IDs with the given kind and parent // combination. kind cannot be empty; parent may be nil. The IDs in the range // returned will not be used by the datastore's automatic ID sequence generator // and may be used with NewKey without conflict. // // The range is inclusive at the low end and exclusive at the high end. In // other words, valid intIDs x satisfy low <= x && x < high. // // If no error is returned, low + n == high. func AllocateIDs(c appengine.Context, kind string, parent *Key, n int) (low, high int64, err error) { if kind == "" { return 0, 0, errors.New("datastore: AllocateIDs given an empty kind") } if n < 0 { return 0, 0, fmt.Errorf("datastore: AllocateIDs given a negative count: %d", n) } if n == 0 { return 0, 0, nil } req := &pb.AllocateIdsRequest{ ModelKey: keyToProto("", NewIncompleteKey(c, kind, parent)), Size: proto.Int64(int64(n)), } res := &pb.AllocateIdsResponse{} if err := c.Call("datastore_v3", "AllocateIds", req, res, nil); err != nil { return 0, 0, err } // The protobuf is inclusive at both ends. Idiomatic Go (e.g. slices, for loops) // is inclusive at the low end and exclusive at the high end, so we add 1. low = res.GetStart() high = res.GetEnd() + 1 if low+int64(n) != high { return 0, 0, fmt.Errorf("datastore: internal error: could not allocate %d IDs", n) } return low, high, nil }
// Put saves src to the index. If id is empty, a new ID is allocated by the // service and returned. If id is not empty, any existing index entry for that // ID is replaced. // // The ID is a human-readable ASCII string. It must contain no whitespace // characters and not start with "!". // // src must be a non-nil struct pointer. func (x *Index) Put(c appengine.Context, id string, src interface{}) (string, error) { fields, err := saveFields(src) if err != nil { return "", err } d := &pb.Document{ Field: fields, } if id != "" { if !validIndexNameOrDocID(id) { return "", fmt.Errorf("search: invalid ID %q", id) } d.Id = proto.String(id) } req := &pb.IndexDocumentRequest{ Params: &pb.IndexDocumentParams{ Document: []*pb.Document{d}, IndexSpec: &x.spec, }, } res := &pb.IndexDocumentResponse{} if err := c.Call("search", "IndexDocument", req, res, nil); err != nil { return "", err } if len(res.Status) != 1 || len(res.DocId) != 1 { return "", fmt.Errorf("search: internal error: wrong number of results (%d Statuses, %d DocIDs)", len(res.Status), len(res.DocId)) } if s := res.Status[0]; s.GetCode() != pb.SearchServiceError_OK { return "", fmt.Errorf("search: %s: %s", s.GetCode(), s.GetErrorDetail()) } return res.DocId[0], nil }
// Get loads the document with the given ID into dst. // // The ID is a human-readable ASCII string. It must be non-empty, contain no // whitespace characters and not start with "!". // // dst must be a non-nil struct pointer or implement the FieldLoadSaver // interface. // // ErrFieldMismatch is returned when a field is to be loaded into a different // type than the one it was stored from, or when a field is missing or // unexported in the destination struct. ErrFieldMismatch is only returned if // dst is a struct pointer. It is up to the callee to decide whether this error // is fatal, recoverable, or ignorable. func (x *Index) Get(c appengine.Context, id string, dst interface{}) error { if id == "" || !validIndexNameOrDocID(id) { return fmt.Errorf("search: invalid ID %q", id) } req := &pb.ListDocumentsRequest{ Params: &pb.ListDocumentsParams{ IndexSpec: &x.spec, StartDocId: proto.String(id), Limit: proto.Int32(1), }, } res := &pb.ListDocumentsResponse{} if err := c.Call("search", "ListDocuments", req, res, nil); err != nil { return err } if res.Status == nil || res.Status.GetCode() != pb.SearchServiceError_OK { return fmt.Errorf("search: %s: %s", res.Status.GetCode(), res.Status.GetErrorDetail()) } if len(res.Document) != 1 || res.Document[0].GetId() != id { return ErrNoSuchDocument } metadata := &DocumentMetadata{ Rank: int(res.Document[0].GetOrderId()), } return loadDoc(dst, res.Document[0].Field, nil, metadata) }
// QueueStats retrieves statistics about queues. func QueueStats(c appengine.Context, queueNames []string) ([]QueueStatistics, error) { req := &pb.TaskQueueFetchQueueStatsRequest{ QueueName: make([][]byte, len(queueNames)), } for i, q := range queueNames { if q == "" { q = "default" } req.QueueName[i] = []byte(q) } res := &pb.TaskQueueFetchQueueStatsResponse{} callOpts := &internal.CallOptions{ Timeout: 10 * time.Second, } if err := c.Call("taskqueue", "FetchQueueStats", req, res, callOpts); err != nil { return nil, err } qs := make([]QueueStatistics, len(res.Queuestats)) for i, qsg := range res.Queuestats { qs[i] = QueueStatistics{ Tasks: int(*qsg.NumTasks), } if eta := *qsg.OldestEtaUsec; eta > -1 { qs[i].OldestETA = time.Unix(0, eta*1e3) } if si := qsg.ScannerInfo; si != nil { qs[i].Executed1Minute = int(*si.ExecutedLastMinute) qs[i].InFlight = int(si.GetRequestsInFlight()) qs[i].EnforcedRate = si.GetEnforcedRate() } } return qs, nil }
// Send sends a message on the channel associated with clientID. func Send(c appengine.Context, clientID, message string) error { req := &pb.SendMessageRequest{ ApplicationKey: &clientID, Message: &message, } resp := &basepb.VoidProto{} return remapError(c.Call(service, "SendChannelMessage", req, resp, nil)) }
// Run starts a query for log records, which contain request and application // level log information. func (params *Query) Run(c appengine.Context) *Result { req, err := makeRequest(params, c.FullyQualifiedAppID(), appengine.VersionID(c)) return &Result{ context: c, request: req, err: err, } }
// DefaultVersion returns the default version of the specified module. // If module is the empty string, it means the default module. func DefaultVersion(c appengine.Context, module string) (string, error) { req := &pb.GetDefaultVersionRequest{} if module != "" { req.Module = &module } res := &pb.GetDefaultVersionResponse{} err := c.Call("modules", "GetDefaultVersion", req, res, nil) return res.GetVersion(), err }
// Create creates a channel and returns a token for use by the client. // The clientID is an application-provided string used to identify the client. func Create(c appengine.Context, clientID string) (token string, err error) { req := &pb.CreateChannelRequest{ ApplicationKey: &clientID, } resp := &pb.CreateChannelResponse{} err = c.Call(service, "CreateChannel", req, resp, nil) token = resp.GetToken() return token, remapError(err) }
// Invite sends an invitation. If the from address is an empty string // the default ([email protected]/bot) will be used. func Invite(c appengine.Context, to, from string) error { req := &pb.XmppInviteRequest{ Jid: &to, } if from != "" { req.FromJid = &from } res := &pb.XmppInviteResponse{} return c.Call("xmpp", "SendInvite", req, res, nil) }
// OAuthConsumerKey returns the OAuth consumer key provided with the current // request. This method will return an error if the OAuth request was invalid. func OAuthConsumerKey(c appengine.Context) (string, error) { req := &pb.CheckOAuthSignatureRequest{} res := &pb.CheckOAuthSignatureResponse{} err := c.Call("user", "CheckOAuthSignature", req, res, nil) if err != nil { return "", err } return *res.OauthConsumerKey, err }
// Purge removes all tasks from a queue. func Purge(c appengine.Context, queueName string) error { if queueName == "" { queueName = "default" } req := &pb.TaskQueuePurgeQueueRequest{ QueueName: []byte(queueName), } res := &pb.TaskQueuePurgeQueueResponse{} return c.Call("taskqueue", "PurgeQueue", req, res, nil) }
// LogoutURL returns a URL that, when visited, signs the user out, // then redirects the user to the URL specified by dest. func LogoutURL(c appengine.Context, dest string) (string, error) { req := &pb.CreateLogoutURLRequest{ DestinationUrl: proto.String(dest), } res := &pb.CreateLogoutURLResponse{} if err := c.Call("user", "CreateLogoutURL", req, res, nil); err != nil { return "", err } return *res.LogoutUrl, nil }
// DefaultBucketName returns the name of this application's // default Google Cloud Storage bucket. func DefaultBucketName(c appengine.Context) (string, error) { req := &aipb.GetDefaultGcsBucketNameRequest{} res := &aipb.GetDefaultGcsBucketNameResponse{} err := c.Call("app_identity_service", "GetDefaultGcsBucketName", req, res, nil) if err != nil { return "", fmt.Errorf("file: no default bucket name returned in RPC response: %v", res) } return res.GetDefaultGcsBucketName(), nil }
// Call invokes a delayed function. // f.Call(c, ...) // is equivalent to // t, _ := f.Task(...) // taskqueue.Add(c, t, "") func (f *Function) Call(c appengine.Context, args ...interface{}) { t, err := f.Task(args...) if err != nil { c.Errorf("%v", err) return } if _, err := taskqueueAdder(c, t, queue); err != nil { c.Errorf("delay: taskqueue.Add failed: %v", err) return } }
// Start starts the specified version of the specified module. // If either module or version are the empty string, it means the default. func Start(c appengine.Context, module, version string) error { req := &pb.StartModuleRequest{} if module != "" { req.Module = &module } if version != "" { req.Version = &version } res := &pb.StartModuleResponse{} return c.Call("modules", "StartModule", req, res, nil) }
// SetNumInstances sets the number of instances of the given module.version to the // specified value. If either module or version are the empty string it means the // default. func SetNumInstances(c appengine.Context, module, version string, instances int) error { req := &pb.SetNumInstancesRequest{} if module != "" { req.Module = &module } if version != "" { req.Version = &version } req.Instances = proto.Int64(int64(instances)) res := &pb.SetNumInstancesResponse{} return c.Call("modules", "SetNumInstances", req, res, nil) }
// LoginURLFederated is like LoginURL but accepts a user's OpenID identifier. func LoginURLFederated(c appengine.Context, dest, identity string) (string, error) { req := &pb.CreateLoginURLRequest{ DestinationUrl: proto.String(dest), } if identity != "" { req.FederatedIdentity = proto.String(identity) } res := &pb.CreateLoginURLResponse{} if err := c.Call("user", "CreateLoginURL", req, res, nil); err != nil { return "", err } return *res.LoginUrl, nil }
// DeleteMulti is a batch version of Delete. func DeleteMulti(c appengine.Context, key []*Key) error { if len(key) == 0 { return nil } if err := multiValid(key); err != nil { return err } req := &pb.DeleteRequest{ Key: multiKeyToProto(c.FullyQualifiedAppID(), key), } res := &pb.DeleteResponse{} return c.Call("datastore_v3", "Delete", req, res, nil) }
// NumInstances returns the number of instances of the given module/version. // If either argument is the empty string it means the default. func NumInstances(c appengine.Context, module, version string) (int, error) { req := &pb.GetNumInstancesRequest{} if module != "" { req.Module = &module } if version != "" { req.Version = &version } res := &pb.GetNumInstancesResponse{} if err := c.Call("modules", "GetNumInstances", req, res, nil); err != nil { return 0, err } return int(*res.Instances), nil }
// GetMulti is a batch version of Get. // // dst must be a []S, []*S, []I or []P, for some struct type S, some interface // type I, or some non-interface non-pointer type P such that P or *P // implements PropertyLoadSaver. If an []I, each element must be a valid dst // for Get: it must be a struct pointer or implement PropertyLoadSaver. // // As a special case, PropertyList is an invalid type for dst, even though a // PropertyList is a slice of structs. It is treated as invalid to avoid being // mistakenly passed when []PropertyList was intended. func GetMulti(c appengine.Context, key []*Key, dst interface{}) error { v := reflect.ValueOf(dst) multiArgType, _ := checkMultiArg(v) if multiArgType == multiArgTypeInvalid { return errors.New("datastore: dst has invalid type") } if len(key) != v.Len() { return errors.New("datastore: key and dst slices have different length") } if len(key) == 0 { return nil } if err := multiValid(key); err != nil { return err } req := &pb.GetRequest{ Key: multiKeyToProto(c.FullyQualifiedAppID(), key), } res := &pb.GetResponse{} if err := c.Call("datastore_v3", "Get", req, res, nil); err != nil { return err } if len(key) != len(res.Entity) { return errors.New("datastore: internal error: server returned the wrong number of entities") } multiErr, any := make(appengine.MultiError, len(key)), false for i, e := range res.Entity { if e.Entity == nil { multiErr[i] = ErrNoSuchEntity } else { elem := v.Index(i) if multiArgType == multiArgTypePropertyLoadSaver || multiArgType == multiArgTypeStruct { elem = elem.Addr() } if multiArgType == multiArgTypeStructPtr && elem.IsNil() { elem.Set(reflect.New(elem.Type().Elem())) } multiErr[i] = loadEntity(elem.Interface(), e.Entity) } if multiErr[i] != nil { any = true } } if any { return multiErr } return nil }
func runFunc(c appengine.Context, w http.ResponseWriter, req *http.Request) { defer req.Body.Close() var inv invocation if err := gob.NewDecoder(req.Body).Decode(&inv); err != nil { c.Errorf("delay: failed decoding task payload: %v", err) c.Warningf("delay: dropping task") return } f := funcs[inv.Key] if f == nil { c.Errorf("delay: no func with key %q found", inv.Key) c.Warningf("delay: dropping task") return } ft := f.fv.Type() in := []reflect.Value{reflect.ValueOf(c)} for _, arg := range inv.Args { var v reflect.Value if arg != nil { v = reflect.ValueOf(arg) } else { // Task was passed a nil argument, so we must construct // the zero value for the argument here. n := len(in) // we're constructing the nth argument var at reflect.Type if !ft.IsVariadic() || n < ft.NumIn()-1 { at = ft.In(n) } else { at = ft.In(ft.NumIn() - 1).Elem() } v = reflect.Zero(at) } in = append(in, v) } out := f.fv.Call(in) if n := ft.NumOut(); n > 0 && ft.Out(n-1) == errorType { if errv := out[n-1]; !errv.IsNil() { c.Errorf("delay: func failed (will retry): %v", errv.Interface()) w.WriteHeader(http.StatusInternalServerError) return } } }
// Run starts a query for log records, which contain request and application // level log information. func (params *Query) Run(c appengine.Context) *Result { req := &pb.LogReadRequest{} appId := c.FullyQualifiedAppID() req.AppId = &appId if !params.StartTime.IsZero() { req.StartTime = proto.Int64(params.StartTime.UnixNano() / 1e3) } if !params.EndTime.IsZero() { req.EndTime = proto.Int64(params.EndTime.UnixNano() / 1e3) } if params.Offset != nil { var offset pb.LogOffset if err := proto.Unmarshal(params.Offset, &offset); err != nil { return &Result{context: c, err: fmt.Errorf("bad Offset: %v", err)} } req.Offset = &offset } if params.Incomplete { req.IncludeIncomplete = ¶ms.Incomplete } if params.AppLogs { req.IncludeAppLogs = ¶ms.AppLogs } if params.ApplyMinLevel { req.MinimumLogLevel = proto.Int32(int32(params.MinLevel)) } if params.Versions == nil { // If no versions were specified, default to the major version // used by this app. versionID := appengine.VersionID(c) if i := strings.Index(versionID, "."); i >= 0 { versionID = versionID[:i] } req.VersionId = []string{versionID} } else { req.VersionId = params.Versions } if params.RequestIDs != nil { ids := make([][]byte, len(params.RequestIDs)) for i, v := range params.RequestIDs { ids[i] = []byte(v) } req.RequestId = ids } return &Result{context: c, request: req} }
// GetPresenceMulti retrieves multiple users' presence. // If the from address is an empty string the default // ([email protected]/bot) will be used. // Possible return values are "", "away", "dnd", "chat", "xa". // If any presence is unavailable, an appengine.MultiError is returned func GetPresenceMulti(c appengine.Context, to []string, from string) ([]string, error) { req := &pb.BulkPresenceRequest{ Jid: to, } if from != "" { req.FromJid = &from } res := &pb.BulkPresenceResponse{} if err := c.Call("xmpp", "BulkGetPresence", req, res, nil); err != nil { return nil, err } presences := make([]string, 0, len(res.PresenceResponse)) errs := appengine.MultiError{} addResult := func(presence string, err error) { presences = append(presences, presence) errs = append(errs, err) } anyErr := false for _, subres := range res.PresenceResponse { if !subres.GetValid() { anyErr = true addResult("", ErrInvalidJID) continue } if !*subres.IsAvailable || subres.Presence == nil { anyErr = true addResult("", ErrPresenceUnavailable) continue } presence, ok := presenceMap[*subres.Presence] if ok { addResult(presence, nil) } else { anyErr = true addResult("", fmt.Errorf("xmpp: unknown presence %q", *subres.Presence)) } } if anyErr { return presences, errs } return presences, nil }
// ModifyLease modifies the lease of a task. // Used to request more processing time, or to abandon processing. // leaseTime is in seconds and must not be negative. func ModifyLease(c appengine.Context, task *Task, queueName string, leaseTime int) error { if queueName == "" { queueName = "default" } req := &pb.TaskQueueModifyTaskLeaseRequest{ QueueName: []byte(queueName), TaskName: []byte(task.Name), EtaUsec: proto.Int64(task.ETA.UnixNano() / 1e3), // Used to verify ownership. LeaseSeconds: proto.Float64(float64(leaseTime)), } res := &pb.TaskQueueModifyTaskLeaseResponse{} if err := c.Call("taskqueue", "ModifyTaskLease", req, res, nil); err != nil { return err } task.ETA = time.Unix(0, *res.UpdatedEtaUsec*1e3) return nil }
// PutMulti is a batch version of Put. // // src must satisfy the same conditions as the dst argument to GetMulti. func PutMulti(c appengine.Context, key []*Key, src interface{}) ([]*Key, error) { v := reflect.ValueOf(src) multiArgType, _ := checkMultiArg(v) if multiArgType == multiArgTypeInvalid { return nil, errors.New("datastore: src has invalid type") } if len(key) != v.Len() { return nil, errors.New("datastore: key and src slices have different length") } if len(key) == 0 { return nil, nil } appID := c.FullyQualifiedAppID() if err := multiValid(key); err != nil { return nil, err } req := &pb.PutRequest{} for i := range key { elem := v.Index(i) if multiArgType == multiArgTypePropertyLoadSaver || multiArgType == multiArgTypeStruct { elem = elem.Addr() } sProto, err := saveEntity(appID, key[i], elem.Interface()) if err != nil { return nil, err } req.Entity = append(req.Entity, sProto) } res := &pb.PutResponse{} if err := c.Call("datastore_v3", "Put", req, res, nil); err != nil { return nil, err } if len(key) != len(res.Key) { return nil, errors.New("datastore: internal error: server returned the wrong number of keys") } ret := make([]*Key, len(key)) for i := range ret { var err error ret[i], err = protoToKey(res.Key[i]) if err != nil || ret[i].Incomplete() { return nil, errors.New("datastore: internal error: server returned an invalid key") } } return ret, nil }