// 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 or // FieldMetadataLoadSaver interface. // // If dst is a struct pointer, then fields which are missing or unexported in // the destination struct are silently ignored. 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) }
// 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) }
// GetPresence retrieves a user's 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". // ErrPresenceUnavailable is returned if the presence is unavailable. func GetPresence(c appengine.Context, to string, from string) (string, error) { req := &xmpp_proto.PresenceRequest{ Jid: &to, } if from != "" { req.FromJid = &from } res := &xmpp_proto.PresenceResponse{} if err := c.Call("xmpp", "GetPresence", req, res, nil); err != nil { return "", err } if !*res.IsAvailable || res.Presence == nil { return "", ErrPresenceUnavailable } switch *res.Presence { case xmpp_proto.PresenceResponse_NORMAL: return "", nil case xmpp_proto.PresenceResponse_AWAY: return "away", nil case xmpp_proto.PresenceResponse_DO_NOT_DISTURB: return "dnd", nil case xmpp_proto.PresenceResponse_CHAT: return "chat", nil case xmpp_proto.PresenceResponse_EXTENDED_AWAY: return "xa", nil } return "", fmt.Errorf("xmpp: unknown presence %v", *res.Presence) }
// Purge removes all tasks from a queue. func Purge(c appengine.Context, queueName string) error { req := &taskqueue_proto.TaskQueuePurgeQueueRequest{ QueueName: []byte(queueName), } res := &taskqueue_proto.TaskQueuePurgeQueueResponse{} return c.Call("taskqueue", "PurgeQueue", req, res, 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 }
// DeleteMulti is a batch version of Delete. // The returned slice will have the same length as the input slice. // If a given key cannot be found, its corresponding value in the // returned error slice is set to ErrCacheMiss. // 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{} e := make([]error, len(key)) err := c.Call("memcache", "Delete", req, res, nil) if err == nil && len(e) != len(res.DeleteStatus) { err = ErrServerError } if err != nil { for i := range e { e[i] = err } return e } for i, s := range res.DeleteStatus { switch s { case pb.MemcacheDeleteResponse_DELETED: e[i] = nil case pb.MemcacheDeleteResponse_NOT_FOUND: e[i] = ErrCacheMiss default: e[i] = ErrServerError } } return e }
// Get loads the entity stored for k into dst, which may be either a struct // pointer or a Map. If there is no such entity for the key, Get returns // ErrNoSuchEntity. // // The values of dst's unmatched struct fields or Map entries are not modified. // In particular, it is recommended to pass either a pointer to a zero valued // struct or an empty Map on each Get call. // // 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. func Get(c appengine.Context, k *Key, dst interface{}) os.Error { if !k.valid() { return ErrInvalidKey } req := &pb.GetRequest{ Key: []*pb.Reference{ keyToProto(c.AppID(), k), }, } res := &pb.GetResponse{} err := c.Call("datastore_v3", "Get", req, res) if err != nil { return err } if len(res.Entity) == 0 || res.Entity[0].Entity == nil { return ErrNoSuchEntity } if m, ok := dst.(Map); ok { return loadMap(m, k, res.Entity[0].Entity) } sv, err := asStructValue(dst) if err != nil { return err } return loadStruct(sv, k, res.Entity[0].Entity) }
func lease(c appengine.Context, maxTasks int, queueName string, leaseTime int, groupByTag bool, tag []byte) ([]*Task, error) { req := &taskqueue_proto.TaskQueueQueryAndOwnTasksRequest{ QueueName: []byte(queueName), LeaseSeconds: proto.Float64(float64(leaseTime)), MaxTasks: proto.Int64(int64(maxTasks)), GroupByTag: proto.Bool(groupByTag), Tag: tag, } res := &taskqueue_proto.TaskQueueQueryAndOwnTasksResponse{} callOpts := &appengine_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 }
// QueueStats retrieves statistics about queues. // If maxTasks is greater than zero, the number of tasks scanned will be limited; // if the number of tasks is greater than maxTasks, the Tasks field will be an approximation. func QueueStats(c appengine.Context, queueNames []string, maxTasks int) ([]QueueStatistics, error) { req := &taskqueue_proto.TaskQueueFetchQueueStatsRequest{ QueueName: make([][]byte, len(queueNames)), } for i, q := range queueNames { req.QueueName[i] = []byte(q) } if maxTasks > 0 { req.MaxNumTasks = proto.Int32(int32(maxTasks)) } res := &taskqueue_proto.TaskQueueFetchQueueStatsResponse{} callOpts := &appengine_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 an email message. func Send(c appengine.Context, msg *Message) error { req := &mail_proto.MailMessage{ Sender: &msg.Sender, To: msg.To, Cc: msg.Cc, Bcc: msg.Bcc, Subject: &msg.Subject, TextBody: &msg.Body, } if msg.ReplyTo != "" { req.ReplyTo = &msg.ReplyTo } if len(msg.Attachments) > 0 { req.Attachment = make([]*mail_proto.MailAttachment, len(msg.Attachments)) for i, att := range msg.Attachments { req.Attachment[i] = &mail_proto.MailAttachment{ FileName: &att.Name, Data: att.Data, } } } res := &struct{}{} // VoidProto if err := c.Call("mail", "Send", req, res, nil); err != nil { return err } return 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) } req := &taskqueue_proto.TaskQueueDeleteRequest{ QueueName: []byte(queueName), TaskName: taskNames, } res := &taskqueue_proto.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 != taskqueue_proto.TaskQueueServiceError_OK { me[i] = &appengine_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 }
// 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 or implement the FieldLoadSaver or // FieldMetadataLoadSaver interface. func (x *Index) Put(c appengine.Context, id string, src interface{}) (string, error) { d, err := saveDoc(src) if err != nil { return "", err } 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) > 0 { if s := res.Status[0]; s.GetCode() != pb.SearchServiceError_OK { return "", fmt.Errorf("search: %s: %s", s.GetCode(), s.GetErrorDetail()) } } 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)) } return res.DocId[0], 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 }
// Delete deletes a file. func Delete(c appengine.Context, filename string) error { req := &filepb.DeleteRequest{ Filename: &filename, } res := new(filepb.DeleteResponse) // No fields in response to check. return c.Call("file", "Delete", req, res, nil) }
// Send sends a message on the channel associated with clientID. func Send(c appengine.Context, clientID, message string) os.Error { req := &channel_proto.SendMessageRequest{ ApplicationKey: &clientID, Message: &message, } resp := &struct{}{} // VoidProto return c.Call(service, "SendChannelMessage", req, resp) }
// Send sends a message on the channel associated with clientID. func Send(c appengine.Context, clientID, message string) error { req := &channel_proto.SendMessageRequest{ ApplicationKey: &clientID, Message: &message, } resp := &base_proto.VoidProto{} return c.Call(service, "SendChannelMessage", req, resp, nil) }
// Convert converts the document to the given MIME type. // opts may be nil. func (d *Document) Convert(c appengine.Context, mimeType string, opts *Options) (*Document, error) { req := &conversion_proto.ConversionRequest{ Conversion: []*conversion_proto.ConversionInput{ &conversion_proto.ConversionInput{ Input: &conversion_proto.DocumentInfo{}, OutputMimeType: &mimeType, }, }, } for _, asset := range d.Assets { a := &conversion_proto.AssetInfo{ Data: asset.Data, } if asset.Name != "" { a.Name = &asset.Name } if asset.Type != "" { a.MimeType = &asset.Type } req.Conversion[0].Input.Asset = append(req.Conversion[0].Input.Asset, a) } if opts != nil { f, err := opts.toFlags() if err != nil { return nil, err } for k, v := range f { req.Conversion[0].Flag = append(req.Conversion[0].Flag, &conversion_proto.ConversionInput_AuxData{ Key: proto.String(k), Value: proto.String(v), }) } } res := &conversion_proto.ConversionResponse{} if err := c.Call("conversion", "Convert", req, res, nil); err != nil { return nil, err } // We only support one conversion at a time, so the following code assumes that. if len(res.Result) != 1 { return nil, fmt.Errorf("conversion: requested conversion of one doc, but got %d back", len(res.Result)) } if ec := *res.Result[0].ErrorCode; ec != conversion_proto.ConversionServiceError_OK { return nil, fmt.Errorf("conversion: operation failed: %v", ec) } output := res.Result[0].Output if output == nil { return nil, errors.New("conversion: output is nil") } doc := &Document{} for _, asset := range output.Asset { doc.Assets = append(doc.Assets, Asset{ Name: asset.GetName(), Data: asset.Data, Type: asset.GetMimeType(), }) } return doc, nil }
// Create creates a channel and returns a token for use by the client. // The clientID is an appication-provided string used to identify the client. func Create(c appengine.Context, clientID string) (token string, err os.Error) { req := &channel_proto.CreateChannelRequest{ ApplicationKey: &clientID, } resp := &channel_proto.CreateChannelResponse{} err = c.Call("channel", "CreateChannel", req, resp) token = proto.GetString(resp.ClientId) return }
// 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 }
// set sets the given items using the given conflict resolution policy. // The returned slice will have the same length as the input slice. // If value is not nil, each element should correspond to an item. func set(c appengine.Context, item []*Item, value [][]byte, policy int32) []os.Error { req := &pb.MemcacheSetRequest{ Item: make([]*pb.MemcacheSetRequest_Item, len(item)), } for i, t := range item { p := &pb.MemcacheSetRequest_Item{ Key: []byte(t.Key), } if value == nil { p.Value = t.Value } else { p.Value = value[i] } if t.Flags != 0 { p.Flags = proto.Uint32(t.Flags) } if t.Expiration != 0 { // In the .proto file, MemcacheSetRequest_Item uses a fixed32 (i.e. unsigned) // for expiration time, while MemcacheGetRequest_Item uses int32 (i.e. signed). // Throughout this .go file, we use int32. p.ExpirationTime = proto.Uint32(uint32(t.Expiration)) } if t.casID != 0 { p.CasId = proto.Uint64(t.casID) p.ForCas = proto.Bool(true) } p.SetPolicy = pb.NewMemcacheSetRequest_SetPolicy(policy) req.Item[i] = p } res := &pb.MemcacheSetResponse{} e := make([]os.Error, len(item)) if err := c.Call("memcache", "Set", req, res); err != nil { for i := range e { e[i] = err } return e } if len(e) != len(res.SetStatus) { for i := range e { e[i] = ErrServerError } return e } for i := range e { switch res.SetStatus[i] { case pb.MemcacheSetResponse_STORED: e[i] = nil case pb.MemcacheSetResponse_NOT_STORED: e[i] = ErrNotStored case pb.MemcacheSetResponse_EXISTS: e[i] = ErrCASConflict default: e[i] = ErrServerError } } return e }
// 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 := &channel_proto.CreateChannelRequest{ ApplicationKey: &clientID, } resp := &channel_proto.CreateChannelResponse{} err = c.Call(service, "CreateChannel", req, resp, nil) token = resp.GetToken() return }
// RunInBackground runs f in a background goroutine in this process. // This is only valid to invoke from a backend. func RunInBackground(c appengine.Context, f func(c appengine.Context)) error { req := &system_proto.StartBackgroundRequestRequest{} res := &system_proto.StartBackgroundRequestResponse{} if err := c.Call("system", "StartBackgroundRequest", req, res, nil); err != nil { return err } sendc <- send{res.GetRequestId(), f} return nil }
// BlobKeyForFile returns a BlobKey for a Google Storage file. // The filename should be of the form "/gs/bucket_name/object_name". func BlobKeyForFile(c appengine.Context, filename string) (appengine.BlobKey, error) { req := &blobpb.CreateEncodedGoogleStorageKeyRequest{ Filename: &filename, } res := &blobpb.CreateEncodedGoogleStorageKeyResponse{} if err := c.Call("blobstore", "CreateEncodedGoogleStorageKey", req, res, nil); err != nil { return "", err } return appengine.BlobKey(*res.BlobKey), 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) }
// 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 }
// 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 }
// UploadURL creates an upload URL for the form that the user will fill out, // passing the application path to load when the POST of the form is // completed. These URLs expire and should not be reused. func UploadURL(c appengine.Context, successPath string) (*http.URL, os.Error) { req := &pb.CreateUploadURLRequest{ SuccessPath: proto.String(successPath), } res := &pb.CreateUploadURLResponse{} if err := c.Call("blobstore", "CreateUploadURL", req, res); err != nil { return nil, err } return http.ParseURL(*res.Url) }
// 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 }