#include <iostream> #include <stasher/client.H> #include <stasher/userinit.H> #include <x/fmtsize.H> #include <vector> #include <string> #include <sstream> void simpleget(int argc, char **argv) { stasher::client client=stasher::client::base::connect(); stasher::userinit limits=client->getlimits(); std::cerr << "Maximum " << limits.maxobjects << " objects, " << x::fmtsize(limits.maxobjectsize) << " aggregate object size, per transaction." << std::endl << "Maximum " << limits.maxsubs << " concurrent subscriptions." << std::endl; stasher::client::base::getreq req =stasher::client::base::getreq::create(); req->openobjects=true; req->objects.insert(argv+1, argv+argc); stasher::contents contents=client->get(req)->objects; if (!contents->succeeded) throw EXCEPTION(contents->errmsg); for (int i=1; i<argc; ++i) { stasher::contents::base::map_t::iterator p= contents->find(argv[i]); std::cout << argv[i]; if (p == contents->end()) { std::cout << ": does not exist" << std::endl; continue; } std::cout << " (uuid " << x::tostring(p->second.uuid) << "): "; x::istream is=p->second.fd->getistream(); std::string firstline; std::getline(*is, firstline); std::cout << firstline << std::endl; } } int main(int argc, char **argv) { try { simpleget(argc, argv); } catch (const x::exception &e) { std::cerr << e << std::endl; exit(1); } return 0; }
stasher::client
is an x::ref
handle that represents a connection
to the object repository server. Following the normal naming conventions for
LIBCXX's reference-counted object, a
stasher::clientptr
is an x::ptr
type.
stasher::client::base::connect
connects to the default object repository node on the server server
and returns the connection handle.
As documented in the class reference, an overloaded
connect
() takes an explicit directory name for an
object repository node, if it's not installed in the stasher library's
default location. If there are more than one object repository nodes,
use defaultnodes
() to enumerate them, then pick
one for the connect
().
connect
() throws an
x::exception
if the connection fails.
Alternatively, connect_client
() allocates a
connection handle, and the connection attempt gets made the first time
the handle gets used in some way.
No explicit error or exception gets reported in the event that the postponed connection attempt fails. Rather, the corresponding request fails with an appropriate error code or indication. When an existing, established, connection with the object repository server breaks, all pending/outstanding requests fail with an appropriate error code, and another connection attempt gets made when the next request is made.
The administrative connection functions that are described in the documentation are available only to root or processes running under the same userid as the object repository node's daemon, and they enable some additional methods that are described in the client handle object's reference documentation; ordinary applications do not need them.
Client connection threads
stasher::client
starts a thread, which handles the connection details. The thread stops automatically upon disconnection from the server. The thread also stops if the connection to stasher server terminates for any reason (but, as described above, it gets restarted automatically, by the next request of any kind).
Connection limits
A single application can create more than one client handle, to different repositories (if multiple repositories exist), or to the same object repository; however object repository servers have a server-configured limit on the maximum number of connections they'll accept from the same application. There are also several per-connection limits. The above example invokes
getlimits
(), and shows what those limits are: a single transaction can update/replace a maximum number of objects in the same transaction, and the total size of all new or replaced objects, in aggregate (meaning the sum total of the number of bytes in the new values of all new or replaced objects) is also limited. There's also a limit on the maximum number of open subscriptions.
getlimits
() only works when a connection to the
server has been established. If the client handle is disconnected,
stasher::userinit
returns all the limits as zeroes.
It's a roundabout way of checking the current status of the client
handle.
stasher::client
->get
(): get objects from the repository
This method takes a
stasher::client::base::getreq
as a
parameter.
This is an x::ref
to an object with two
documented members: a
bool
openobjects
(defaults to
false
); and a
std::set <std::string>
objects
, which enumerates the names of objects
to retrieve from the object repository.
objects
lists the names of objects to retrieve, in one
transaction. They cannot exceed the limit on the maximum number of objects
in a transaction.
The objects are arranged in a hierarchy, with “/” as a hierarchy separator, like a filesystem but with two key differences: there is no concept of a “current directory” and all object names are absolute, without the leading “/”; and there is no explicit equivalent to creating or removing directories. Put an object named “fruits/apple”, and the “fruits” hierarchy appears, if it did not exist before. Remove the “fruits/apple” object, and the “fruits” hierarchy disappears unless some other object is still in there.
Although stasher does not explicitly interpret the objects in any way,
aside from implementing the object hierarchy, the client convention
is to use the UTF-8
codeset.
()
returns an stasher::client
->getx::ref
to an object that's not
particularly interesting except for its
objects
member, which is a
stasher::contents
.
This is an x::ref
to a subclass of
a std::map
, specifically a
stasher::contents::base::map_t
.
But before getting there, check
succeeded
first.
It's a bool
, with true
indicating a succesful request. If false
, look at
the std::string
error message in
errmsg
.
If an object name that was placed in
stasher::client::base::getreq
's
objects
is not in
stasher::contents
, this means that the object
does not exist. If it does exist,
the map's second
is a
stasher::retrobj which has
two members: an x::uuid
uuid
that gives the object's
“uuid”, and an
x::fdptr
fd
.
When an object gets added to the object repository, it acquires a
x::uuid
assigned to it by the server, and it
gets returned here, by
().
stasher::client
->getfd
is an open read-only file descriptor, if
stasher::client::base::getreq
's
openobjects
was true
file descriptor. If it was false
, only each object's
x::uuid
gets returned, and the
x::fdptr
is null.
The returned file descriptor is an open read-only file descriptor to
the actual object file in the repository. The application should
not sit on it, but read it, and get rid of it, as in this example
(since the x::fd
is a reference-counted object,
this gets taken care of when the reference goes out of scope completely
and gets destroyed). Even if the object gets deleted from the
repository, this file descriptor will remain open, and the object's
space on disk remains used, until the file descriptor gets closed.
stasher::client
->put
(): update objects in the repository#include <iostream> #include <stasher/client.H> void simpleput(int argc, char **argv) { if (argc < 2) throw EXCEPTION("Usage: simpleput {object} {value}"); stasher::client client=stasher::client::base::connect(); stasher::client::base::getreq req =stasher::client::base::getreq::create(); req->objects.insert(argv[1]); stasher::contents contents=client->get(req)->objects; if (!contents->succeeded) throw EXCEPTION(contents->errmsg); stasher::client::base::transaction tran= stasher::client::base::transaction::create(); auto existing=contents->find(argv[1]); const char *what; if (existing != contents->end()) { if (argc > 2) { what="updated"; tran->updobj(argv[1], existing->second.uuid, argv[2]); } else { what="deleted"; tran->delobj(argv[1], existing->second.uuid); } } else { if (argc > 2) tran->newobj(argv[1], argv[2]); else throw EXCEPTION("Object doesn't exist anyway"); what="created"; } stasher::putresults results=client->put(tran); if (results->status == stasher::req_processed_stat) { std::cout << "Object " << what << ", new uuid: " << x::tostring(results->newuuid) << std::endl; } else { throw EXCEPTION(x::tostring(results->status)); } } int main(int argc, char **argv) { try { simpleput(argc, argv); } catch (const x::exception &e) { std::cerr << e << std::endl; exit(1); } return 0; }
This method takes a
stasher::client::base::transaction
as a
parameter.
This is an x::ref
to an object that defines which
objects the transaction updates.
This example takes the name of an object and its new value as command
line parameters.
get
()
gets invoked to check if the object already exists. Note that in this
case,
stasher::client::base::getreq
's
openobjects
flag does not get set to
true
.
Here the existing object's contents are not needed, this is just a check
if the object already exists, and what it's x::uuid
is.
stasher::client::base::transaction
is just a structure
(it's an x::ref
, actually)
that holds a list of repository objects that gets updated by the
transaction. The actual transaction gets executed by
put
().
Instantiate a
stasher::client::base::transaction
and invoke its
methods, as follows:
newobj
(name
, value
)Add a new object to the object repository.
updobj
(name
, uuid
, value
)Update/replace an existing object in the object repository. The new contents replace the object's existing contents.
delobj
(name
, uuid
)Delete an existing object from the object repository.
These methods can be invoked more than once (for different objects) to have a single transaction update multiple objects. The same transaction can add, replace, or delete different objects. There's a server-imposed limit on the maximum number of objects in one transaction.
name
gives the name of the object that the
transaction adds, replaces, or deleted. uuid
specifies an existing object's x::uuid
.
A value
for a new or replaced object is a
std::string
or an opened
x::fd
whose contents specify the object's value.
There's an limit on the total size
of all
new or replaced objects in the transaction.
Object names
Objects are identified by their name. Object names have a maximum size of 512 bytes, and they use “/” for a hierarchy separator, so “group/files” would represent an object named “files” in the “group” hierarchy. Object names are always absolute, but they do not begin with a “/” like an absolute pathname. There is no formal process to create or delete a hierarchy, that's analogous to creating or deleting a directory; create the object “group/files” and the “group” hierarchy gets created; remove it, and if there are no other objects in the “group” hierarchy, it gets removed. Although stasher itself does not impose any other standards on object names, other than their maximum size and “/” as a hierarchy separator, by convention object names should be coded in
UTF-8
. The stasher tool usesUTF-8
for object names, so application should useUTF-8
coding too, in order for stasher to be available for diagnostics.
()
returns a
stasher::putresults
which contains two fields:
stasher::client
->putstatus
, an enumerated status code,
stasher::req_stat_t
;
and newuuid
, the new x::uuid
of
objects added or replaced by the transaction, if it succeeded.
Object uuids
Each object in a stasher object repository has a “universal unique identifier”, uuid for short. The server assigns a uuid to each object added to the repository, and when an existing object's contents get replaced. A transaction
status
ofstasher::req_processed_stat
indicates that the transaction was processed, and stasher returns anewuuid
, thex::uuid
of new or updated objects. The samex::uuid
applies to all objects added or updated by the transaction.An existing object's uuid must be specified to update or delete it. A transaction
status
ofstasher::req_rejected_stat
indicates that the transaction was rejected because one of the existing objects in the transaction did not match it's givenx::uuid
. This includes anewobj
() of an object that already exists, and anupdobj
() or adelobj
() of a nonexistent object.The transaction gets rejected if any of its objects' uuids do not match. None of the objects in the transaction get updated even if some of the uuids matched.
The above example calls get
() first, to
check if an object exists. If so, the transaction replaces it with
updobj
(), specifying
its uuid. If the object does not exist, the transaction creates it with
newobj
(). The object gets deleted with
delobj
() by omitting the second parameter to
the example program.
It's possible that another application can change its object between the
time this sample program gets its uuid, and sends the transaction request.
Since the object's uuid no longer matches, the update transaction will
get rejected, with stasher::req_rejected_stat
.
This simplistic example prints the newuuid
after
deleting the object. Of course, in this case, the object got deleted,
and the newuuid
is meaningless. However, if the
same trasnaction created or updated other objects, the given
newuuid
spplies to them.
Transaction status
Other stasher C++ API requests also report their success or failure by a status code. Generally,
stasher::req_processed_stat
indicates that the request was processed succesfully. Besidesstasher::req_rejected_stat
indicating a transaction uuid mismatch, the status code may also report other error conditions.