/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ /* $Rev$ $Date$ */ #ifndef tuscany_filedb_hpp #define tuscany_filedb_hpp #include #include #include #include "string.hpp" #include "list.hpp" #include "value.hpp" #include "monad.hpp" #include "fstream.hpp" #include "element.hpp" #include "../../modules/scheme/eval.hpp" #include "../../modules/xml/xml.hpp" #include "../../modules/json/json.hpp" namespace tuscany { namespace filedb { /** * Convert a database name to an absolute path. */ const string absdbname(const string& name) { if (length(name) == 0 || c_str(name)[0] == '/') return name; char cwd[512]; if (getcwd(cwd, sizeof(cwd)) == NULL) return name; return string(cwd) + "/" + name; } /** * Represents a FileDB connection. */ class FileDB { public: FileDB() : owner(false) { } FileDB(const string& name, const string& format) : owner(true), name(absdbname(name)), format(format) { debug(name, "filedb::filedb::name"); debug(format, "filedb::filedb::format"); } FileDB(const FileDB& c) : owner(false), name(c.name), format(c.format) { debug("filedb::filedb::copy"); } FileDB& operator=(const FileDB& c) = delete; ~FileDB() { } private: unused const bool owner; const string name; const string format; friend const failable write(const value& v, ostream& os, const string& format); friend const failable read(istream& is, const string& format); friend const failable post(const value& key, const value& val, const FileDB& db); friend const failable put(const value& key, const value& val, const FileDB& db); friend const failable patch(const value& key, const value& val, const FileDB& db); friend const failable get(const value& key, const FileDB& db); friend const failable del(const value& key, const FileDB& db); }; /** * Convert a key to a file name. */ const string filename(const list& path, const string& root) { if (isNull(path)) return root; const string name = root + "/" + (isString(car(path))? (string)car(path) : write(content(scheme::writeValue(car(path))))); return filename(cdr(path), name); } const string filename(const value& key, const string& root) { if (!isList(key)) return filename(mklist(key), root); return filename((list)key, root); } /** * Make the parent directories of a keyed file. */ const failable mkdirs(const list& path, const string& root) { if (isNull(cdr(path))) return true; const string dir = root + "/" + (isString(car(path))? (string)car(path) : write(content(scheme::writeValue(car(path))))); mkdir(c_str(dir), S_IRWXU); return mkdirs(cdr(path), dir); } /** * Write a value to a database file. */ const failable write(const value& v, ostream& os, const string& format) { if (format == "scheme") { const string vs(write(content(scheme::writeValue(v)))); os << vs; return true; } if (format == "xml") { failable > s = xml::writeElements(valuesToElements(v)); if (!hasContent(s)) return mkfailure(s); write(content(s), os); return true; } if (format == "json") { failable > s = json::writeValue(v); if (!hasContent(s)) return mkfailure(s); write(content(s), os); return true; } return mkfailure(string("Unsupported database format: ") + format); } /** * Read a value from a database file. */ const failable read(istream& is, const string& format) { if (format == "scheme") { return scheme::readValue(streamList(is)); } if (format == "xml") { const failable > fv = xml::readElements(streamList(is)); if (!hasContent(fv)) return mkfailure(fv); const value v = elementsToValues(content(fv)); return v; } if (format == "json") { return json::readValue(streamList(is)); } return mkfailure(string("Unsupported database format: ") + format); } /** * Post a new item to the database. */ const failable post(const value& key, const value& val, const FileDB& db) { debug(key, "filedb::post::key"); debug(val, "filedb::post::value"); debug(db.name, "filedb::post::dbname"); if (isList(key)) mkdirs(key, db.name); const string fn = filename(key, db.name); debug(fn, "filedb::post::filename"); ofstream os(fn); if (os.fail()) { ostringstream os; os << "Couldn't post file database entry: " << key; return mkfailure(str(os)); } const failable r = write(val, os, db.format); debug(r, "filedb::post::result"); return r; } /** * Update an item in the database. If the item doesn't exist it is added. */ const failable put(const value& key, const value& val, const FileDB& db) { debug(key, "filedb::put::key"); debug(val, "filedb::put::value"); debug(db.name, "filedb::put::dbname"); if (isList(key)) mkdirs(key, db.name); const string fn = filename(key, db.name); debug(fn, "filedb::put::filename"); ofstream os(fn); if (os.fail()) { ostringstream os; os << "Couldn't put file database entry: " << key; return mkfailure(str(os)); } const failable r = write(val, os, db.format); debug(r, "filedb::put::result"); return r; } /** * Patch an item in the database. If the item doesn't exist it is added. */ const failable patch(const value& key, const value& val, const FileDB& db) { debug(key, "filedb::patch::key"); debug(val, "filedb::patch::value"); debug(db.name, "filedb::patch::dbname"); if (isList(key)) mkdirs(key, db.name); const string fn = filename(key, db.name); debug(fn, "filedb::patch::filename"); ofstream os(fn); if (os.fail()) { ostringstream os; os << "Couldn't patch file database entry: " << key; return mkfailure(str(os)); } const failable r = write(val, os, db.format); debug(r, "filedb::patch::result"); return r; } /** * Get an item from the database. */ const failable get(const value& key, const FileDB& db) { debug(key, "filedb::get::key"); debug(db.name, "filedb::get::dbname"); const string fn = filename(key, db.name); debug(fn, "filedb::get::filename"); ifstream is(fn); if (is.fail()) { ostringstream os; os << "Couldn't get file database entry: " << key; return mkfailure(str(os), 404, false); } const failable val = read(is, db.format); debug(val, "filedb::get::result"); return val; } /** * Delete an item from the database */ const failable del(const value& key, const FileDB& db) { debug(key, "filedb::delete::key"); debug(db.name, "filedb::delete::dbname"); const string fn = filename(key, db.name); debug(fn, "filedb::del::filename"); const int rc = unlink(c_str(fn)); if (rc == -1) { ostringstream os; os << "Couldn't delete file database entry: " << key; return errno == ENOENT? mkfailure(str(os), 404, false) : mkfailure(str(os)); } debug(true, "filedb::delete::result"); return true; } } } #endif /* tuscany_filedb_hpp */