Esempio n. 1
0
func (s *S) TestNewObjectId(c *C) {
	// Generate 10 ids
	ids := make([]bson.ObjectId, 10)
	for i := 0; i < 10; i++ {
		ids[i] = bson.NewObjectId()
	}
	for i := 1; i < 10; i++ {
		prevId := ids[i-1]
		id := ids[i]
		// Test for uniqueness among all other 9 generated ids
		for j, tid := range ids {
			if j != i {
				c.Assert(id, Not(Equals), tid, Bug("Generated ObjectId is not unique"))
			}
		}
		// Check that timestamp was incremented and is within 30 seconds of the previous one
		td := id.Timestamp() - prevId.Timestamp()
		c.Assert((td >= 0 && td <= 30), Equals, true, Bug("Wrong timestamp in generated ObjectId"))
		// Check that machine ids are the same
		c.Assert(id.Machine(), Equals, prevId.Machine())
		// Check that pids are the same
		c.Assert(id.Pid(), Equals, prevId.Pid())
		// Test for proper increment
		delta := int(id.Counter() - prevId.Counter())
		c.Assert(delta, Equals, 1, Bug("Wrong increment in generated ObjectId"))
	}
}
Esempio n. 2
0
// Create creates a new file with the provided name in the GridFS.  If the file
// name already exists, a new version will be inserted with an up-to-date
// uploadDate that will cause it to be atomically visible to the Open and
// OpenId methods.  If the file name is not important, an empty name may be
// provided and the file Id used instead.
//
// It's important to Close files whether they are being written to
// or read from, and to check the err result to ensure the operation
// completed successfully.
//
// A simple example inserting a new file:
//
//     func check(err os.Error) {
//         if err != nil {
//             panic(err.String())
//         }
//     }
//     file, err := db.GridFS("fs").Create("myfile.txt")
//     check(err)
//     n, err := file.Write([]byte("Hello world!")
//     check(err)
//     err = file.Close()
//     check(err)
//     fmt.Printf("%d bytes written\n", n)
//
// The io.Writer interface is implemented by *GridFile and may be used to
// help on the file creation.  For example:
//
//     file, err := db.GridFS("fs").Create("myfile.txt")
//     check(err)
//     messages, err := os.Open("/var/log/messages")
//     check(err)
//     defer messages.Close()
//     err = io.Copy(file, messages)
//     check(err)
//     err = file.Close()
//     check(err)
//
func (gfs GridFS) Create(name string) (file *GridFile, err os.Error) {
	file = gfs.newFile()
	file.mode = gfsWriting
	file.wsum = md5.New()
	file.doc = gfsFile{Id: bson.NewObjectId(), ChunkSize: 256 * 1024, Filename: name}
	return
}
Esempio n. 3
0
func (file *GridFile) insertChunk(data []byte) {
	n := file.chunk
	file.chunk++
	debugf("GridFile %p: adding to checksum: %q", file, string(data))
	file.wsum.Write(data)

	for file.doc.ChunkSize*file.wpending >= 1024*1024 {
		// Hold on.. we got a MB pending.
		file.c.Wait()
		if file.err != nil {
			return
		}
	}

	file.wpending++

	debugf("GridFile %p: inserting chunk %d with %d bytes", file, n, len(data))

	// We may not own the memory of data, so rather than
	// simply copying it, we'll marshal the document ahead of time.
	data, err := bson.Marshal(gfsChunk{bson.NewObjectId(), file.doc.Id, n, data})
	if err != nil {
		file.err = err
		return
	}

	go func() {
		err := file.gfs.Chunks.Insert(bson.Raw{Data: data})
		file.m.Lock()
		file.wpending--
		if err != nil && file.err == nil {
			file.err = err
		}
		file.c.Broadcast()
		file.m.Unlock()
	}()
}
Esempio n. 4
0
func genRoutes() []artichoke.Route {

	/*
		Order of the requests are important. They will be executed top-down.
	*/

	return []artichoke.Route{

		/* GET requests */

		artichoke.Route{
			Method:  "GET",
			Pattern: "/:db/:collection/:docid",
			Handler: func(w http.ResponseWriter, r *http.Request, m artichoke.Data) bool {
				// create a new session based on the global one; this allows different login details
				s := session.New()
				defer s.Close()

				query := m["query"].(url.Values)
				params := m["params"].(map[string]string)

				c := session.DB(params["db"]).C(params["collection"])

				id, resEnded := decodeId(params["docid"], query, w)
				if resEnded {
					return true
				}

				// we'll need this query later
				q := c.Find(bson.M{"_id": id})
				n, err := q.Count()
				if err != nil {
					writeError(w, 500, "Error counting results")
					return true
				} else if n == 0 {
					writeError(w, 404, "Document not found")
					return true
				}

				// we don't know the structure before-hand, but we know it's a JSON object
				var out map[string]interface{}
				err = q.One(&out)
				if err != nil {
					writeError(w, 500, "Error getting document by id")
					return true
				}

				res, err := json.Marshal(out)
				if err != nil {
					writeError(w, 500, "Error stringifying query result")
					return true
				}

				writeJSON(w, 200, string(res))
				return true
			},
		},
		artichoke.Route{
			Method:  "GET",
			Pattern: "/:db/:collection",
			Handler: func(w http.ResponseWriter, r *http.Request, m artichoke.Data) bool {
				// create a new session based on the global one; this allows different login details
				s := session.New()
				defer s.Close()

				params := m["params"].(map[string]string)
				query := m["query"].(url.Values)

				// get the collection the user specified
				c := session.DB(params["db"]).C(params["collection"])

				// check for query params
				if len(query) > 0 {
					// not yet implemented
					writeError(w, 500, "Query params not supported yet")
					return true
				} else {
					// no query parameters means they want everything in the collection
					var out []map[string]interface{}
					err := c.Find(nil).All(&out)
					if err != nil {
						writeError(w, 500, "Error getting all documents")
						return true
					}

					res, err := json.Marshal(out)
					if err != nil {
						writeError(w, 500, "Error stringifying response")
						return true
					}

					writeJSON(w, 200, string(res))
					return true
				}

				return false
			},
		},
		artichoke.Route{
			Method:  "GET",
			Pattern: "/:db",
			Handler: func(w http.ResponseWriter, r *http.Request, m artichoke.Data) bool {
				// create a new session per connection
				s := session.New()
				defer s.Close()

				params := m["params"].(map[string]string)
				names, err := session.DB(params["db"]).CollectionNames()
				if err != nil {
					writeError(w, 500, "Error getting collection names")
					return true
				}

				writeJSON(w, 200, fmt.Sprintf("[%s]", strings.Join(names, ",")))
				return true
			},
		},
		artichoke.Route{
			Method:  "GET",
			Pattern: "/",
			Handler: func(w http.ResponseWriter, r *http.Request, m artichoke.Data) bool {
				// create a new session per connection
				s := session.New()
				defer s.Close()

				names, err := s.DatabaseNames()
				if err != nil {
					writeError(w, 500, "Error getting database names")
					return true
				}

				writeJSON(w, 200, fmt.Sprintf("[%s]", strings.Join(names, ",")))
				return true
			},
		},

		/* DELETE Requests */

		artichoke.Route{
			Method:  "DELETE",
			Pattern: "/:db/:collection/:docid",
			Handler: func(w http.ResponseWriter, r *http.Request, m artichoke.Data) bool {
				if m["bodyJson"] != nil {
					writeError(w, 400, "DELETE requests on document ids takes no parameters, and thus should have no body or query parameters.")
					return true
				}

				// create a new session per connection
				s := session.New()
				defer s.Close()

				// get the params and query objects
				query := m["query"].(url.Values)
				params := m["params"].(map[string]string)

				c := s.DB(params["db"]).C(params["collection"])

				id, resEnded := decodeId(params["docid"], query, w)
				if resEnded {
					return true
				}

				obj := bson.M{"_id": id}

				n, err := c.Find(obj).Count()
				if err != nil {
					writeError(w, 500, "Error communicating with database")
					return true
				}

				if n == 0 {
					writeError(w, 404, "The id provided was not found in this database. Either the document has been deleted already or it never existed.")
					return true
				}

				err = c.Remove(obj)
				if err != nil {
					writeError(w, 500, "Error removing item from database")
					return true
				}

				w.Header().Add("Content-Length", "0")
				w.WriteHeader(204)
				return true
			},
		},
		artichoke.Route{
			Method:  "DELETE",
			Pattern: "/:db/:collection",
			Handler: func(w http.ResponseWriter, r *http.Request, m artichoke.Data) bool {
				if m["bodyJson"] != nil {
					writeError(w, 500, "DELETE requests with parameters is not supported yet")
					return true
				}

				// create a new session per connection
				s := session.New()
				defer s.Close()

				params := m["params"].(map[string]string)
				err := s.DB(params["db"]).C(params["collection"]).DropCollection()
				if err != nil {
					writeError(w, 500, "Error dropping collection")
					return true
				}

				w.Header().Add("Content-Length", "0")
				w.WriteHeader(204)
				return true
			},
		},
		artichoke.Route{
			Method:  "DELETE",
			Pattern: "/:db",
			Handler: func(w http.ResponseWriter, r *http.Request, m artichoke.Data) bool {
				if m["bodyJson"] != nil {
					writeError(w, 500, "DELETE requests with parameters is not supported yet")
					return true
				}

				// create a new session per connection
				s := session.New()
				defer s.Close()

				params := m["params"].(map[string]string)
				err := s.DB(params["db"]).DropDatabase()
				if err != nil {
					writeError(w, 500, "Error dropping database")
					return true
				}

				w.Header().Add("Content-Length", "0")
				w.WriteHeader(204)
				return true
			},
		},

		/* POST Requests */

		// Update a document in place. This will return an error if the document does not exist.
		artichoke.Route{
			Method:  "POST",
			Pattern: "/:db/:collection",
			Handler: func(w http.ResponseWriter, r *http.Request, m artichoke.Data) bool {
				fmt.Println("Post handler called")

				// create a new session per connection
				s := session.New()
				defer s.Close()

				// get the params and query objects
				params := m["params"].(map[string]string)
				query := m["query"].(url.Values)

				// get the colletion
				c := s.DB(params["db"]).C(params["collection"])

				body := m["bodyJson"].(map[string]interface{})
				if body["_id"] != nil {
					id, resEnded := decodeId(body["_id"].(string), query, w)
					if resEnded {
						return true
					}

					// ensure that body has the correct id
					body["_id"] = id

					// update, if possible
					err := c.Update(bson.M{"_id": id}, body)
					if err == mgo.NotFound {
						insert(body, &c, w)
						return true
					} else if err != nil {
						writeError(w, 500, "Error updating document in database")
						return true
					} else {
						// update succeeded
						// TODO: make this return the current data in the database?
						w.Header().Add("Content-Length", "0")
						w.WriteHeader(204)
						return true
					}
				} else {
					// generate a new ObjectId
					body["_id"] = bson.NewObjectId()

					// and create it
					insert(body, &c, w)
					return true
				}

				return false
			},
		},

		/* PUT Requests */

		// Since databases and collections are created on-the-fly, there's no PUT for a database or collection.
		//
		// A PUT request will update the URI (overwriting an existing resource), so Insert is used.
		// Note that the document id is mandatory. For an auto-generated ID, POST to the collection instead.
		//
		// For updates, use POST instead (on an existing resource).
		artichoke.Route{
			Method:  "PUT",
			Pattern: "/:db/:collection/:docid",
			Handler: func(w http.ResponseWriter, r *http.Request, m artichoke.Data) bool {
				_, bodyExists := m["bodyJson"]
				if !bodyExists {
					writeError(w, 400, "No valid body supplied or body sent with the wrong content-type. Must send as application/json.")
					return true
				}

				// create a new session per connection
				s := session.New()
				defer s.Close()

				// get the params and query objects
				params := m["params"].(map[string]string)
				query := m["query"].(url.Values)

				id, resEnded := decodeId(params["docid"], query, w)
				if resEnded {
					return true
				}

				// get the collection so we can check if the id exists
				c := s.DB(params["db"]).C(params["collection"])

				// try finding as hex
				n, err := c.Find(bson.M{"_id": id}).Count()
				if err != nil {
					writeError(w, 500, "Error communicating with database")
					return true
				}

				// put the id in
				body := m["bodyJson"].(map[string]interface{})
				body["_id"] = id

				// get the preferred status code
				var status int
				if n > 0 {
					status = 204
				} else {
					status = 201
				}

				// insert into the database
				// we won't use the one defined above, because we don't want to return anything to the user
				err = c.Insert(body)
				if err != nil {
					writeError(w, 500, "Error inserting document into database")
					return true
				}

				// PUT requests should not return anything on success
				w.Header().Add("Content-Length", "0")
				w.WriteHeader(status)
				return true
			},
		},
	}
}