/*
* FileStorage.cc
* Read and write Atoms to a file.
*
* Copyright (c) 2021 Linas Vepstas <linas@linas.org>
*
* LICENSE:
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License v3 as
* published by the Free Software Foundation and including the exceptions
* at http://opencog.org/wiki/Licenses
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
*
* You should have received a copy of the GNU Affero General Public License
* along with this program; if not, write to:
* Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
//#include <error.h>
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
#include <fstream>
#include <opencog/atomspace/AtomSpace.h>
#include <opencog/persist/sexpr/Sexpr.h>
#include <opencog/persist/storage/storage_types.h>
#include "fast_load.h"
#include "FileStorage.h"
using namespace opencog;
FileStorageNode::FileStorageNode(Type t, const std::string& uri)
: StorageNode(t, uri)
{
_already_loaded = false;
_fh = nullptr;
_filename = get_name();
// If the URL begins with `file://` then just strip that off.
if (0 == _filename.compare(0, 7, "file://"))
_filename = _filename.substr(7);
}
FileStorageNode::~FileStorageNode()
{
if (_fh) fclose(_fh);
_fh = nullptr;
}
void FileStorageNode::erase(void)
{
if (not connected())
throw IOException(TRACE_INFO,
"FileStorageNode %s is not open!", _filename.c_str());
int rc = ftruncate(fileno(_fh), 0);
if (rc)
throw IOException(TRACE_INFO,
"FileStorageNode cannot erase %s: %s",
_filename.c_str(), strerror(errno));
}
void FileStorageNode::kill_data(void)
{
if (_fh) erase();
else
{
int rc = unlink(_filename.c_str());
if (rc)
throw IOException(TRACE_INFO,
"FileStorageNode cannot remove %s: %s",
_filename.c_str(), strerror(errno));
}
}
void FileStorageNode::open(void)
{
if (_fh)
throw IOException(TRACE_INFO,
"FileStorageNode %s is already open!", _filename.c_str());
_already_loaded = false;
_fh = fopen(_filename.c_str(), "a+");
// If we get a "Permission denied", then try again in read-only mode.
if (nullptr == _fh and (EPERM == errno or EACCES == errno))
_fh = fopen(_filename.c_str(), "r");
if (nullptr == _fh)
throw IOException(TRACE_INFO,
"FileStorageNode cannot open %s: %s",
_filename.c_str(), strerror(errno));
}
void FileStorageNode::close(void)
{
if (_fh) fclose(_fh);
_fh = nullptr;
_already_loaded = false;
}
bool FileStorageNode::connected(void)
{
return nullptr != _fh;
}
void FileStorageNode::barrier(AtomSpace*)
{
if (_fh) fflush(_fh);
}
Handle FileStorageNode::getNode(Type, const char *)
{
throw IOException(TRACE_INFO,
"FileStorageNode does not support this operation!");
return Handle::UNDEFINED;
}
Handle FileStorageNode::getLink(Type, const HandleSeq&)
{
throw IOException(TRACE_INFO,
"FileStorageNode does not support this operation!");
return Handle::UNDEFINED;
}
void FileStorageNode::fetchIncomingSet(AtomSpace* as, const Handle&)
{
// Fake it.
return loadAtomSpace(as);
// throw IOException(TRACE_INFO,
// "FileStorageNode does not support this operation!");
}
void FileStorageNode::fetchIncomingByType(AtomSpace* as, const Handle&, Type)
{
// Fake it.
return loadAtomSpace(as);
// throw IOException(TRACE_INFO,
// "FileStorageNode does not support this operation!");
}
void FileStorageNode::storeAtom(const Handle& h, bool synchronous)
{
if (not connected())
throw IOException(TRACE_INFO,
"FileStorageNode %s is not open!", _filename.c_str());
const std::string sex = Sexpr::dump_atom(h);
fwrite(sex.c_str(), sex.length(), 1, _fh);
size_t rc = fwrite("\n", 1, 1, _fh);
if (1 != rc)
throw IOException(TRACE_INFO,
"FileStorageNode failed to store Atom at %s: %s",
_filename.c_str(), strerror(errno));
}
void FileStorageNode::removeAtom(AtomSpace* as, const Handle&, bool recursive)
{
throw IOException(TRACE_INFO,
"FileStorageNode does not support this operation!");
}
void FileStorageNode::storeValue(const Handle& h, const Handle& key)
{
if (not connected())
throw IOException(TRACE_INFO,
"FileStorageNode %s is not open!", _filename.c_str());
const std::string sex = Sexpr::dump_vatom(h, key);
fwrite(sex.c_str(), sex.length(), 1, _fh);
size_t rc = fwrite("\n", 1, 1, _fh);
if (1 != rc)
throw IOException(TRACE_INFO,
"FileStorageNode failed to store Atom at %s: %s",
_filename.c_str(), strerror(errno));
}
void FileStorageNode::loadValue(const Handle&, const Handle&)
{
throw IOException(TRACE_INFO,
"FileStorageNode does not support this operation!");
}
void FileStorageNode::loadType(AtomSpace* as, Type)
{
// Fake it.
return loadAtomSpace(as);
// throw IOException(TRACE_INFO,
// "FileStorageNode does not support this operation!");
}
void FileStorageNode::storeAtomSpace(const AtomSpace* table)
{
if (not connected())
throw IOException(TRACE_INFO,
"FileStorageNode %s is not open!", _filename.c_str());
HandleSeq hset;
table->get_handles_by_type(hset, ATOM, true);
for(const Handle& h: hset)
{
// Store roots, and Atoms that have values.
// All other Atoms will appear in outgoing sets.
if (h->haveValues() or 0 == h->getIncomingSetSize())
storeAtom(h);
}
fflush(_fh);
}
void FileStorageNode::loadAtomSpace(AtomSpace* table)
{
// Avoid reading twice.
if (_already_loaded) return;
// Check to see if it's connected, and then ignore the file handle.
if (not connected())
throw IOException(TRACE_INFO,
"FileStorageNode %s is not open!", _filename.c_str());
std::ifstream stream(_filename);
if (not stream.is_open())
throw IOException(TRACE_INFO,
"FileStorageNode cannot open %s", _filename.c_str());
parseStream(stream, *table);
stream.close();
_already_loaded = true;
}
DEFINE_NODE_FACTORY(FileStorageNode, FILE_STORAGE_NODE)