2018-01-24 21:29:39 -08:00
|
|
|
#include <iostream>
|
|
|
|
|
#include <string>
|
|
|
|
|
#include <tuple>
|
2018-01-29 01:43:33 -08:00
|
|
|
#include <algorithm>
|
2018-01-24 21:29:39 -08:00
|
|
|
|
2018-01-29 01:43:33 -08:00
|
|
|
#include "database.h"
|
|
|
|
|
#include "io_primitives.h"
|
|
|
|
|
#include "utils.h"
|
2018-01-25 21:43:10 -08:00
|
|
|
|
2018-01-24 21:29:39 -08:00
|
|
|
#include <boost/iostreams/device/file.hpp>
|
2018-01-25 21:43:10 -08:00
|
|
|
#ifdef ZLIB_FOUND
|
2018-01-24 21:29:39 -08:00
|
|
|
#include <boost/iostreams/filter/zlib.hpp>
|
|
|
|
|
#include <boost/iostreams/filter/gzip.hpp>
|
2018-01-25 21:43:10 -08:00
|
|
|
#endif
|
|
|
|
|
#ifdef BZIP2_FOUND
|
2018-01-24 21:29:39 -08:00
|
|
|
#include <boost/iostreams/filter/bzip2.hpp>
|
2018-01-25 21:43:10 -08:00
|
|
|
#endif
|
2018-01-24 21:29:39 -08:00
|
|
|
#include <boost/iostreams/filter/counter.hpp>
|
|
|
|
|
|
|
|
|
|
using namespace std::literals::string_literals;
|
|
|
|
|
|
|
|
|
|
bool verbose = false;
|
|
|
|
|
|
2018-01-29 01:43:33 -08:00
|
|
|
database read_db_labelsv1(istream &, std::uint32_t);
|
2018-01-24 21:29:39 -08:00
|
|
|
database read_db_oldstyle(istream &, std::uint32_t);
|
|
|
|
|
void write_db_labelsv1(std::ostream &, const database &);
|
|
|
|
|
|
|
|
|
|
std::string
|
|
|
|
|
istream_line(const istream &in)
|
|
|
|
|
{
|
2018-01-29 01:43:33 -08:00
|
|
|
int line = in.component<boost::iostreams::counter>(0)->lines();
|
2018-01-24 21:29:39 -08:00
|
|
|
return " at line "s + std::to_string(line);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
dbtype
|
|
|
|
|
dbtype_from_num(int n)
|
|
|
|
|
{
|
|
|
|
|
switch (n) {
|
|
|
|
|
case 0x1:
|
|
|
|
|
return dbtype::ROOM;
|
|
|
|
|
case 0x2:
|
|
|
|
|
return dbtype::THING;
|
|
|
|
|
case 0x4:
|
|
|
|
|
return dbtype::EXIT;
|
|
|
|
|
case 0x8:
|
|
|
|
|
return dbtype::PLAYER;
|
|
|
|
|
case 0x10:
|
|
|
|
|
return dbtype::GARBAGE;
|
|
|
|
|
default:
|
|
|
|
|
throw std::runtime_error{"Unknown type: "s + std::to_string(n)};
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int
|
|
|
|
|
dbtype_to_num(dbtype t)
|
|
|
|
|
{
|
|
|
|
|
switch (t) {
|
|
|
|
|
case dbtype::ROOM:
|
|
|
|
|
return 0x1;
|
|
|
|
|
case dbtype::THING:
|
|
|
|
|
return 0x2;
|
|
|
|
|
case dbtype::EXIT:
|
|
|
|
|
return 0x4;
|
|
|
|
|
case dbtype::PLAYER:
|
|
|
|
|
return 0x8;
|
|
|
|
|
case dbtype::GARBAGE:
|
|
|
|
|
return 0x10;
|
|
|
|
|
}
|
|
|
|
|
return 0x10;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void
|
2018-01-29 01:43:33 -08:00
|
|
|
database::fix_up()
|
2018-01-24 21:29:39 -08:00
|
|
|
{
|
|
|
|
|
/* Flags that we don't have to do anything special about when
|
|
|
|
|
upgrading:
|
|
|
|
|
|
2018-01-29 01:43:33 -08:00
|
|
|
DBF_NO_TEMPLE
|
2018-01-24 21:29:39 -08:00
|
|
|
DBF_NO_STARTUP_FLAG
|
|
|
|
|
DBF_PANIC
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
/* Flags handled in this function:
|
|
|
|
|
|
2018-01-29 01:43:33 -08:00
|
|
|
The absence of both DBF_NEW_LOCKS and DBF_SPIFFY_LOCKS
|
2018-01-24 21:29:39 -08:00
|
|
|
DBF_VALUE_IS_COST
|
|
|
|
|
DBF_LINK_ANYWHERE
|
|
|
|
|
DBF_AF_NODUMP
|
|
|
|
|
DBF_HEAR_CONNECT
|
|
|
|
|
DBF_POWERS_LOGGED
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
/* Flags handled by reader functions:
|
|
|
|
|
|
|
|
|
|
DBF_NO_CHAT_SYSTEM
|
|
|
|
|
DBF_WARNINGS
|
|
|
|
|
DBF_CREATION_TIMES
|
2018-01-29 01:43:33 -08:00
|
|
|
DBF_NO_POWERS
|
2018-01-25 00:16:32 -08:00
|
|
|
DBF_NEW_LOCKS
|
2018-01-29 01:43:33 -08:00
|
|
|
DBF_NEW_STRINGS
|
|
|
|
|
DBF_TYPE_GARBAGE
|
|
|
|
|
DBF_SPLIT_IMMORTAL
|
2018-01-25 00:16:32 -08:00
|
|
|
DBF_LESS_GARBAGE
|
2018-01-29 01:43:33 -08:00
|
|
|
DBF_AF_VISUAL
|
|
|
|
|
DBF_VALUE_IS_COST
|
2018-01-24 21:29:39 -08:00
|
|
|
DBF_SPIFFY_LOCKS
|
|
|
|
|
DBF_NEW_FLAGS
|
|
|
|
|
DBF_NEW_POWERS
|
|
|
|
|
DBF_LABELS
|
|
|
|
|
DBF_NEW_VERSIONS
|
2018-01-29 01:43:33 -08:00
|
|
|
DBF_SPIFFY_AF_ANSI - Not set in outputed db unless already present
|
2018-01-24 21:29:39 -08:00
|
|
|
*/
|
|
|
|
|
|
2018-01-29 01:43:33 -08:00
|
|
|
bool oldold_locks = (dbflags & (DBF_NEW_LOCKS | DBF_SPIFFY_LOCKS)) == 0;
|
|
|
|
|
|
|
|
|
|
if (!(dbflags & DBF_POWERS_LOGGED)) {
|
|
|
|
|
for (auto &p : powers) {
|
2018-01-24 21:29:39 -08:00
|
|
|
p.second.perms.insert("log");
|
|
|
|
|
}
|
|
|
|
|
}
|
2018-01-29 01:43:33 -08:00
|
|
|
|
|
|
|
|
if (version < 2) {
|
|
|
|
|
attribs.emplace(
|
|
|
|
|
"MONIKER",
|
|
|
|
|
attrib("MONIKER", split_words_vec("no_command wizard visual locked")));
|
2018-01-24 21:29:39 -08:00
|
|
|
}
|
|
|
|
|
|
2018-01-29 01:43:33 -08:00
|
|
|
if (version < 4) {
|
|
|
|
|
flags["HAVEN"].types.erase("ROOM");
|
2018-01-24 21:29:39 -08:00
|
|
|
}
|
|
|
|
|
|
2018-01-29 01:43:33 -08:00
|
|
|
if (version < 5) {
|
|
|
|
|
attribs.emplace(
|
|
|
|
|
"MAILQUOTA", attrib("MAILQUOTA", split_words_vec(
|
|
|
|
|
"no_command no_clone wizard locked")));
|
2018-01-24 21:29:39 -08:00
|
|
|
}
|
|
|
|
|
|
2018-01-29 01:43:33 -08:00
|
|
|
if (version < 6) {
|
|
|
|
|
powers.erase("Cemit");
|
|
|
|
|
powers.erase("@cemit");
|
2018-01-24 21:45:11 -08:00
|
|
|
}
|
2018-01-24 21:29:39 -08:00
|
|
|
|
2018-01-29 01:43:33 -08:00
|
|
|
for (auto &obj : objects) {
|
2018-01-24 21:29:39 -08:00
|
|
|
|
2018-01-29 01:43:33 -08:00
|
|
|
flags.erase("GOING");
|
|
|
|
|
flags.erase("GOING_TWICE");
|
|
|
|
|
|
|
|
|
|
if (!(dbflags & DBF_AF_NODUMP)) {
|
|
|
|
|
attribs.erase("QUEUE");
|
|
|
|
|
attribs.erase("SEMAPHORE");
|
2018-01-24 21:29:39 -08:00
|
|
|
}
|
2018-01-24 21:45:11 -08:00
|
|
|
|
2018-01-29 01:43:33 -08:00
|
|
|
if (version < 6) {
|
|
|
|
|
powers.erase("Cemit");
|
2018-01-24 21:45:11 -08:00
|
|
|
}
|
2018-01-29 01:43:33 -08:00
|
|
|
|
|
|
|
|
if (oldold_locks) {
|
|
|
|
|
// Pre NEW_LOCKS: Clone enter lock to zone lock on zone objects
|
|
|
|
|
if (obj.zone != NOTHING) {
|
|
|
|
|
auto zlock = objects[obj.zone].locks.find("Zone");
|
|
|
|
|
if (zlock == objects[obj.zone].locks.end()) {
|
|
|
|
|
auto elock = objects[obj.zone].locks.find("Enter");
|
|
|
|
|
if (elock != objects[obj.zone].locks.end()) {
|
|
|
|
|
objects[obj.zone].locks.emplace("Zone", elock->second);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2018-01-24 21:29:39 -08:00
|
|
|
switch (obj.type) {
|
|
|
|
|
case dbtype::THING:
|
2018-01-29 01:43:33 -08:00
|
|
|
if (!(dbflags & DBF_VALUE_IS_COST)) {
|
2018-01-24 21:29:39 -08:00
|
|
|
obj.pennies = (obj.pennies + 1) * 5;
|
|
|
|
|
}
|
|
|
|
|
break;
|
2018-01-29 01:43:33 -08:00
|
|
|
|
2018-01-24 21:29:39 -08:00
|
|
|
case dbtype::PLAYER:
|
|
|
|
|
obj.flags.erase("CONNECTED");
|
2018-01-29 01:43:33 -08:00
|
|
|
if (!(dbflags & DBF_HEAR_CONNECT && obj.flags.count("MONITOR"))) {
|
2018-01-24 21:29:39 -08:00
|
|
|
obj.flags.erase("MONITOR");
|
|
|
|
|
obj.flags.insert("HEAR_CONNECT");
|
|
|
|
|
}
|
2018-01-29 01:43:33 -08:00
|
|
|
|
|
|
|
|
if (oldold_locks) {
|
|
|
|
|
// Pre NEW_LOCKS: Clone Use lock to Page lock
|
|
|
|
|
auto ulock = obj.locks.find("Use");
|
|
|
|
|
if (ulock != obj.locks.end()) {
|
|
|
|
|
obj.locks.emplace("Page", ulock->second);
|
|
|
|
|
}
|
|
|
|
|
// And clone enter lock to zone on ZMPs
|
|
|
|
|
if (obj.flags.count("SHARED")) {
|
|
|
|
|
auto elock = obj.locks.find("Enter");
|
|
|
|
|
if (elock != obj.locks.end()) {
|
|
|
|
|
obj.locks.emplace("Zone", elock->second);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2018-01-24 21:29:39 -08:00
|
|
|
break;
|
2018-01-29 01:43:33 -08:00
|
|
|
|
2018-01-24 21:29:39 -08:00
|
|
|
case dbtype::ROOM:
|
2018-01-29 01:43:33 -08:00
|
|
|
if (version < 4) {
|
2018-01-24 21:29:39 -08:00
|
|
|
obj.flags.erase("HAVEN");
|
|
|
|
|
}
|
2018-01-29 01:43:33 -08:00
|
|
|
|
|
|
|
|
if (oldold_locks) {
|
|
|
|
|
// Pre NEW_LOCKS: Move enter lock to teleport
|
|
|
|
|
auto elock = obj.locks.find("Enter");
|
|
|
|
|
if (elock != obj.locks.end()) {
|
|
|
|
|
obj.locks.emplace("Teleport", elock->second);
|
|
|
|
|
obj.locks.erase(elock);
|
|
|
|
|
}
|
|
|
|
|
}
|
2018-01-24 21:29:39 -08:00
|
|
|
break;
|
2018-01-29 01:43:33 -08:00
|
|
|
|
2018-01-24 21:29:39 -08:00
|
|
|
case dbtype::EXIT:
|
2018-01-29 01:43:33 -08:00
|
|
|
if (obj.location == AMBIGUOUS && !(dbflags & DBF_LINK_ANYWHERE)) {
|
2018-01-24 21:29:39 -08:00
|
|
|
obj.powers.insert("LINK_ANYWHERE");
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
default:
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
2018-01-29 01:43:33 -08:00
|
|
|
version = CURRENT_DB_VERSION;
|
2018-01-24 21:29:39 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::pair<std::uint32_t, const char *> dbflag_table[] = {
|
|
|
|
|
{DBF_NO_CHAT_SYSTEM, "no-chat-system"},
|
|
|
|
|
{DBF_WARNINGS, "warnings"},
|
|
|
|
|
{DBF_CREATION_TIMES, "creation-times"},
|
|
|
|
|
{DBF_NO_POWERS, "no-powers"},
|
|
|
|
|
{DBF_NEW_LOCKS, "new-locks"},
|
|
|
|
|
{DBF_NEW_STRINGS, "new-string"},
|
|
|
|
|
{DBF_TYPE_GARBAGE, "garbage"},
|
|
|
|
|
{DBF_SPLIT_IMMORTAL, "split-immortal"},
|
|
|
|
|
{DBF_NO_TEMPLE, "no-temple"},
|
|
|
|
|
{DBF_LESS_GARBAGE, "less-garbage"},
|
|
|
|
|
{DBF_AF_VISUAL, "af_visual"},
|
|
|
|
|
{DBF_VALUE_IS_COST, "value-is-cost"},
|
|
|
|
|
{DBF_LINK_ANYWHERE, "link-anywhere"},
|
2018-01-29 01:43:33 -08:00
|
|
|
{DBF_NO_STARTUP_FLAG, "no-startup-flag"},
|
2018-01-24 21:29:39 -08:00
|
|
|
{DBF_PANIC, "PANIC"},
|
|
|
|
|
{DBF_AF_NODUMP, "af_nodump"},
|
|
|
|
|
{DBF_SPIFFY_LOCKS, "spiffy-locks"},
|
|
|
|
|
{DBF_NEW_FLAGS, "new-flags"},
|
|
|
|
|
{DBF_NEW_POWERS, "new-powers"},
|
|
|
|
|
{DBF_LABELS, "labels"},
|
|
|
|
|
{DBF_SPIFFY_AF_ANSI, "spiffy-af_ansi"},
|
|
|
|
|
{DBF_HEAR_CONNECT, "hear_connect"},
|
|
|
|
|
{DBF_NEW_VERSIONS, "new-versions"},
|
2018-01-29 01:43:33 -08:00
|
|
|
{0, nullptr}};
|
2018-01-24 21:29:39 -08:00
|
|
|
|
|
|
|
|
std::string
|
2018-01-29 01:43:33 -08:00
|
|
|
dbflags_to_str(std::uint32_t bits)
|
|
|
|
|
{
|
2018-01-25 21:43:10 -08:00
|
|
|
stringset flags;
|
2018-01-24 21:29:39 -08:00
|
|
|
for (int i = 0; dbflag_table[i].second; i += 1) {
|
|
|
|
|
if (dbflag_table[i].first & bits) {
|
|
|
|
|
flags.insert(dbflag_table[i].second);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return join_words(flags);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
istream &
|
|
|
|
|
operator>>(istream &in, database &db)
|
|
|
|
|
{
|
|
|
|
|
char c1, c2;
|
|
|
|
|
|
|
|
|
|
in.get(c1);
|
|
|
|
|
in.get(c2);
|
|
|
|
|
if (!(c1 == '+' && c2 == 'V')) {
|
|
|
|
|
throw db_format_exception{"Invalid database format"};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::uint32_t flags = ((db_getref(in) - 2) / 256) - 5;
|
2018-01-29 01:43:33 -08:00
|
|
|
std::uint32_t minimum_flags = 0;
|
2018-01-24 21:29:39 -08:00
|
|
|
|
|
|
|
|
if (verbose) {
|
2018-01-24 21:45:11 -08:00
|
|
|
std::cerr << "Present database flags: " << dbflags_to_str(flags) << '\n';
|
2018-01-24 21:29:39 -08:00
|
|
|
}
|
2018-01-29 01:43:33 -08:00
|
|
|
|
2018-01-24 21:29:39 -08:00
|
|
|
if ((flags & minimum_flags) != minimum_flags) {
|
2018-01-29 01:43:33 -08:00
|
|
|
throw db_format_exception{
|
|
|
|
|
"Unable to read this database version. Minimum flags: "s +
|
|
|
|
|
dbflags_to_str(minimum_flags)};
|
2018-01-24 21:29:39 -08:00
|
|
|
}
|
2018-01-29 01:43:33 -08:00
|
|
|
|
2018-01-24 21:29:39 -08:00
|
|
|
if (flags & DBF_LABELS) {
|
2018-01-29 01:43:33 -08:00
|
|
|
db = read_db_labelsv1(in, flags);
|
2018-01-24 21:45:11 -08:00
|
|
|
if (verbose) {
|
2018-01-29 01:43:33 -08:00
|
|
|
std::cerr << "Database version " << db.version << '\n';
|
2018-01-24 21:45:11 -08:00
|
|
|
}
|
2018-01-24 21:29:39 -08:00
|
|
|
} else {
|
|
|
|
|
db = read_db_oldstyle(in, flags);
|
|
|
|
|
}
|
2018-01-29 01:43:33 -08:00
|
|
|
db.dbflags = flags;
|
2018-01-24 21:29:39 -08:00
|
|
|
return in;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
database
|
|
|
|
|
read_database(const std::string &name, COMP compress_type, bool vrbse)
|
|
|
|
|
{
|
|
|
|
|
namespace io = boost::iostreams;
|
|
|
|
|
database db;
|
|
|
|
|
|
|
|
|
|
verbose = vrbse;
|
2018-01-29 01:43:33 -08:00
|
|
|
|
2018-01-24 21:29:39 -08:00
|
|
|
istream dbin;
|
2018-01-25 00:16:32 -08:00
|
|
|
dbin.push(io::counter{1});
|
2018-01-29 01:43:33 -08:00
|
|
|
|
2018-01-24 21:29:39 -08:00
|
|
|
switch (compress_type) {
|
2018-01-25 21:43:10 -08:00
|
|
|
case COMP::NONE:
|
2018-01-24 21:29:39 -08:00
|
|
|
break;
|
2018-01-29 01:43:33 -08:00
|
|
|
#ifdef ZLIB_FOUND
|
2018-01-24 21:29:39 -08:00
|
|
|
case COMP::GZ:
|
|
|
|
|
dbin.push(io::gzip_decompressor{});
|
|
|
|
|
break;
|
2018-01-25 21:43:10 -08:00
|
|
|
#endif
|
|
|
|
|
#ifdef BZIP2_FOUND
|
2018-01-24 21:29:39 -08:00
|
|
|
case COMP::BZ2:
|
|
|
|
|
dbin.push(io::bzip2_decompressor{});
|
|
|
|
|
break;
|
2018-01-25 21:43:10 -08:00
|
|
|
#endif
|
2018-01-24 21:29:39 -08:00
|
|
|
default:
|
2018-01-29 01:43:33 -08:00
|
|
|
throw std::runtime_error{"Unsupported compression type!"};
|
2018-01-24 21:29:39 -08:00
|
|
|
break;
|
|
|
|
|
}
|
2018-01-29 01:43:33 -08:00
|
|
|
|
2018-01-24 21:29:39 -08:00
|
|
|
if (name == "-") {
|
|
|
|
|
dbin.push(std::cin);
|
|
|
|
|
} else {
|
2018-01-29 01:43:33 -08:00
|
|
|
if (verbose) {
|
|
|
|
|
std::cerr << "Reading from " << name << '\n';
|
|
|
|
|
}
|
2018-03-12 18:49:17 -07:00
|
|
|
dbin.push(io::file_source{name, std::ios_base::in | std::ios_base::binary});
|
2018-01-24 21:29:39 -08:00
|
|
|
}
|
|
|
|
|
|
2018-01-29 01:43:33 -08:00
|
|
|
dbin.exceptions(std::istream::badbit);
|
|
|
|
|
|
|
|
|
|
dbin.peek();
|
2018-01-24 21:29:39 -08:00
|
|
|
if (!dbin) {
|
|
|
|
|
throw std::runtime_error{"Unable to read database."};
|
|
|
|
|
}
|
2018-01-29 01:43:33 -08:00
|
|
|
|
2018-01-24 21:29:39 -08:00
|
|
|
dbin >> db;
|
|
|
|
|
if (dbin.good() || dbin.eof()) {
|
|
|
|
|
return db;
|
|
|
|
|
} else {
|
|
|
|
|
throw std::runtime_error{"Unable to read database."};
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
write_database(const database &db, const std::string &name, COMP compress_type)
|
|
|
|
|
{
|
|
|
|
|
namespace io = boost::iostreams;
|
|
|
|
|
io::filtering_ostream dbout;
|
|
|
|
|
|
|
|
|
|
switch (compress_type) {
|
2018-01-25 21:43:10 -08:00
|
|
|
case COMP::NONE:
|
2018-01-24 21:29:39 -08:00
|
|
|
break;
|
2018-01-29 01:43:33 -08:00
|
|
|
#ifdef ZLIB_FOUND
|
2018-01-24 21:29:39 -08:00
|
|
|
case COMP::GZ:
|
|
|
|
|
dbout.push(io::gzip_compressor{});
|
|
|
|
|
break;
|
2018-01-25 21:43:10 -08:00
|
|
|
#endif
|
|
|
|
|
#ifdef BZIP2_FOUND
|
2018-01-24 21:29:39 -08:00
|
|
|
case COMP::BZ2:
|
|
|
|
|
dbout.push(io::bzip2_compressor{});
|
|
|
|
|
break;
|
2018-01-25 21:43:10 -08:00
|
|
|
#endif
|
2018-01-24 21:29:39 -08:00
|
|
|
default:
|
2018-01-29 01:43:33 -08:00
|
|
|
throw std::runtime_error{"Unsupported compression type!\n"};
|
|
|
|
|
return;
|
2018-01-24 21:29:39 -08:00
|
|
|
}
|
2018-01-29 01:43:33 -08:00
|
|
|
|
2018-01-24 21:29:39 -08:00
|
|
|
if (name == "-") {
|
|
|
|
|
dbout.push(std::cout);
|
|
|
|
|
} else {
|
2018-03-12 18:49:17 -07:00
|
|
|
dbout.push(io::file_sink{name, std::ios_base::out | std::ios_base::binary});
|
2018-01-24 21:29:39 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!dbout) {
|
|
|
|
|
throw std::runtime_error{"Unable to write database!"};
|
|
|
|
|
}
|
2018-01-29 01:43:33 -08:00
|
|
|
|
2018-01-24 21:29:39 -08:00
|
|
|
dbout << db;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::ostream &
|
|
|
|
|
operator<<(std::ostream &out, const database &db)
|
|
|
|
|
{
|
|
|
|
|
write_db_labelsv1(out, db);
|
|
|
|
|
return out;
|
|
|
|
|
}
|