// 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) }
// 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 }
// 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 }
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 }
// 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 }
// 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 }
// 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 }
// 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) }
// 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)) }
// 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) }
// 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 }
// 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 }
// 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 }
// 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 }
// 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) }
// 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) }
// 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 runOnce(c appengine.Context, f func(appengine.Context) error, opts *TransactionOptions) error { // Begin the transaction. t := &transaction{Context: c} req := &pb.BeginTransactionRequest{ App: proto.String(c.FullyQualifiedAppID()), } if opts != nil && opts.XG { req.AllowMultipleEg = proto.Bool(true) } if err := t.Context.Call("datastore_v3", "BeginTransaction", req, &t.transaction, nil); err != nil { return err } // Call f, rolling back the transaction if f returns a non-nil error, or panics. // The panic is not recovered. defer func() { if t.finished { return } t.finished = true // Ignore the error return value, since we are already returning a non-nil // error (or we're panicking). c.Call("datastore_v3", "Rollback", &t.transaction, &basepb.VoidProto{}, nil) }() if err := f(t); err != nil { return err } t.finished = true // Commit the transaction. res := &pb.CommitResponse{} err := c.Call("datastore_v3", "Commit", &t.transaction, res, nil) if ae, ok := err.(*internal.APIError); ok { if appengine.IsDevAppServer() { // The Python Dev AppServer raises an ApplicationError with error code 2 (which is // Error.CONCURRENT_TRANSACTION) and message "Concurrency exception.". if ae.Code == int32(pb.Error_BAD_REQUEST) && ae.Detail == "ApplicationError: 2 Concurrency exception." { return ErrConcurrentTransaction } } if ae.Code == int32(pb.Error_CONCURRENT_TRANSACTION) { return ErrConcurrentTransaction } } return err }
// 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 }
// 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 }
// 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 }
// Stats retrieves the current memcache statistics. func Stats(c appengine.Context) (*Statistics, error) { req := &pb.MemcacheStatsRequest{} res := &pb.MemcacheStatsResponse{} if err := c.Call("memcache", "Stats", req, res, nil); err != nil { return nil, err } if res.Stats == nil { return nil, ErrNoStats } return &Statistics{ Hits: *res.Stats.Hits, Misses: *res.Stats.Misses, ByteHits: *res.Stats.ByteHits, Items: *res.Stats.Items, Bytes: *res.Stats.Bytes, Oldest: int64(*res.Stats.OldestItemAge), }, nil }
// CurrentOAuth returns the user associated with the OAuth consumer making this // request. If the OAuth consumer did not make a valid OAuth request, or the // scope is non-empty and the current user does not have this scope, this method // will return an error. func CurrentOAuth(c appengine.Context, scope string) (*User, error) { req := &pb.GetOAuthUserRequest{} if scope != "" { req.Scope = &scope } res := &pb.GetOAuthUserResponse{} err := c.Call("user", "GetOAuthUser", req, res, nil) if err != nil { return nil, err } return &User{ Email: *res.Email, AuthDomain: *res.AuthDomain, Admin: res.GetIsAdmin(), ID: *res.UserId, }, nil }
// AddMulti adds multiple tasks to a named queue. // An empty queue name means that the default queue will be used. // AddMulti returns a slice of equivalent tasks with defaults filled in, including setting // each task's Name field to the chosen name if the original was empty. // If a given task is badly formed or could not be added, an appengine.MultiError is returned. func AddMulti(c appengine.Context, tasks []*Task, queueName string) ([]*Task, error) { req := &pb.TaskQueueBulkAddRequest{ AddRequest: make([]*pb.TaskQueueAddRequest, len(tasks)), } me, any := make(appengine.MultiError, len(tasks)), false for i, t := range tasks { req.AddRequest[i], me[i] = newAddReq(c, t, queueName) any = any || me[i] != nil } if any { return nil, me } res := &pb.TaskQueueBulkAddResponse{} if err := c.Call("taskqueue", "BulkAdd", req, res, nil); err != nil { return nil, err } if len(res.Taskresult) != len(tasks) { return nil, errors.New("taskqueue: server error") } tasksOut := make([]*Task, len(tasks)) for i, tr := range res.Taskresult { tasksOut[i] = new(Task) *tasksOut[i] = *tasks[i] tasksOut[i].Method = tasksOut[i].method() if tasksOut[i].Name == "" { tasksOut[i].Name = string(tr.ChosenTaskName) } if *tr.Result != pb.TaskQueueServiceError_OK { if alreadyAddedErrors[*tr.Result] { me[i] = ErrTaskAlreadyAdded } else { me[i] = &internal.APIError{ Service: "taskqueue", Code: int32(*tr.Result), } } any = true } } if any { return tasksOut, me } return tasksOut, nil }