322 lines
6.6 KiB
C++
322 lines
6.6 KiB
C++
///
|
|
/// \file Buffer.cc
|
|
/// \author kyle
|
|
/// \created 10/10/23
|
|
/// \brief Buffer implementation.
|
|
///
|
|
/// \section COPYRIGHT
|
|
/// Copyright 2023 K. Isom <kyle@imap.cc>
|
|
///
|
|
/// Permission to use, copy, modify, and/or distribute this software for
|
|
/// any purpose with or without fee is hereby granted, provided that the
|
|
/// above copyright notice and this permission notice appear in all copies.
|
|
///
|
|
/// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
|
|
/// WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
|
|
/// WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR
|
|
/// BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES
|
|
/// OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
|
|
/// WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
|
|
/// ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
|
|
/// SOFTWARE.
|
|
///
|
|
|
|
|
|
#include <filesystem>
|
|
#include <iterator>
|
|
#include <optional>
|
|
#include <random>
|
|
#include <sstream>
|
|
|
|
#include "Buffer.h"
|
|
|
|
|
|
static constexpr auto statusOK = Buffer::FileStatus::FileStatusOK;
|
|
|
|
|
|
static std::string
|
|
anonymousName()
|
|
{
|
|
std::uniform_int_distribution<> dist(1000, 9999);
|
|
std::random_device randomDevice;
|
|
std::mt19937 rng(randomDevice());
|
|
std::stringstream ss;
|
|
|
|
ss << "Buffer<" << dist(rng) << ">";
|
|
return ss.str();
|
|
}
|
|
|
|
|
|
Buffer::Buffer()
|
|
: dirty(false), name(anonymousName()), status(statusOK)
|
|
{
|
|
}
|
|
|
|
|
|
Buffer::Buffer(std::string fName)
|
|
: dirty(false), name(std::move(fName))
|
|
{
|
|
|
|
}
|
|
|
|
|
|
Buffer::Buffer(std::filesystem::path fPath)
|
|
: dirty(false), path(OptString(fPath.string()))
|
|
{
|
|
this->name = fPath.filename().string();
|
|
this->cursor = Cursor();
|
|
this->file = OptFile(fPath.string());
|
|
// N.B. I am leaving this in to show that I thought about it, but
|
|
// it's the wrong choice. A Frame should call Refresh on a buffer
|
|
// when it's ready to load it.
|
|
// if (this->Exists()) {
|
|
// /// \todo Should I signal an error here, or is it
|
|
// /// okay for this to be a best-effort thing?
|
|
// this->status = this->Refresh();
|
|
// }
|
|
}
|
|
|
|
|
|
|
|
Buffer::Buffer(std::string fName, std::string fPath)
|
|
: dirty(false), name(std::move(fName)), path(fPath)
|
|
{
|
|
if (this->path) {
|
|
this->file = OptFile (File(fPath));
|
|
}
|
|
|
|
}
|
|
|
|
|
|
Buffer::FileStatus
|
|
Buffer::Flush(OptString altPath)
|
|
{
|
|
OptOutFileStream handle = std::nullopt;
|
|
|
|
if (altPath) {
|
|
auto altFile = File(altPath.value());
|
|
handle = altFile.Flush();
|
|
} else if (this->file) {
|
|
handle = this->file.value().Flush();
|
|
} else {
|
|
// At this point, we have no alternate path to write out
|
|
// and this Buffer isn't file-backed. From this point
|
|
// forward, we can operate under the assumption that
|
|
// `this->file` is a valid file.
|
|
return Buffer::FileStatus::FileStatusVirtual;
|
|
}
|
|
|
|
auto realFile = this->file.value();
|
|
if (!handle) {
|
|
if (realFile.IsReadOnly()) {
|
|
return Buffer::FileStatus::FileStatusReadOnly;
|
|
}
|
|
|
|
if (!realFile.IsWriteable()) {
|
|
return Buffer::FileStatus::FileStatusInvalidPermissions;
|
|
}
|
|
|
|
realFile.MarkReadOnly();
|
|
return Buffer::FileStatus::FileStatusIOFailed;
|
|
}
|
|
|
|
/// At this point, we know we're working with a valid file handle.
|
|
auto realHandle = handle.value();
|
|
if (!realHandle->good()) {
|
|
return Buffer::FileStatus::FileStatusIOFailed;
|
|
}
|
|
|
|
for (auto line : this->contents) {
|
|
std::copy(line.begin(), line.end(),
|
|
std::ostream_iterator<uint8_t>(*realHandle, ""));
|
|
}
|
|
|
|
realHandle->flush();
|
|
realHandle->close();
|
|
delete realHandle;
|
|
this->dirty = false;
|
|
return Buffer::FileStatus::FileStatusOK;
|
|
}
|
|
|
|
|
|
Buffer::FileStatus
|
|
Buffer::Refresh()
|
|
{
|
|
/// We can't actually refresh a virtual buffer. Unlike flush,
|
|
/// it doesn't make sense to Refresh from an alternate file.
|
|
if (this->IsVirtual()) {
|
|
return Buffer::FileStatus::FileStatusVirtual;
|
|
}
|
|
|
|
auto realFile = this->file.value();
|
|
auto handle = realFile.Refresh();
|
|
if (!handle) {
|
|
return FileStatus::FileStatusNonExistent;
|
|
}
|
|
|
|
auto realHandle = handle.value();
|
|
this->clearContents();
|
|
this->dirty = false; // clean slate, kiddo
|
|
|
|
/// \todo Moral quandary: if the file disappears from disk, we
|
|
/// probably want to know that, but maybe clearing the
|
|
/// contents first isn't the right move. Maybe mark the
|
|
/// file as ReadOnly?
|
|
if (!realHandle->good()) {
|
|
realHandle->close();
|
|
delete realHandle;
|
|
return FileStatus::FileStatusIOFailed;
|
|
}
|
|
|
|
while (realHandle->good()) {
|
|
std::string temp;
|
|
while (std::getline(*realHandle, temp)) {
|
|
std::vector<uint8_t> row(temp.begin(), temp.end());
|
|
row.push_back('\n');
|
|
this->contents.push_back(row);
|
|
}
|
|
}
|
|
|
|
auto status = Buffer::FileStatus::FileStatusOK;
|
|
if (!realHandle->eof()) {
|
|
status = FileStatus::FileStatusIOFailed;
|
|
}
|
|
realHandle->close();
|
|
delete realHandle;
|
|
|
|
return status;
|
|
}
|
|
|
|
|
|
void
|
|
Buffer::ChangePath(std::string newPath)
|
|
{
|
|
this->path = OptString(std::move(newPath));
|
|
}
|
|
|
|
|
|
void
|
|
Buffer::clearContents()
|
|
{
|
|
for (auto & line : this->contents) {
|
|
line.clear();
|
|
}
|
|
|
|
this->contents.clear();
|
|
}
|
|
|
|
|
|
void
|
|
Buffer::MarkDirty()
|
|
{
|
|
if (this->dirty) {
|
|
return;
|
|
}
|
|
|
|
this->dirty = true;
|
|
}
|
|
|
|
|
|
size_t
|
|
Buffer::Size()
|
|
{
|
|
size_t size = 0;
|
|
|
|
for (const auto& line : this->contents) {
|
|
size += line.size();
|
|
}
|
|
|
|
return size;
|
|
}
|
|
|
|
|
|
void
|
|
Buffer::Close()
|
|
{
|
|
this->clearContents();
|
|
this->dirty = false;
|
|
this->file = std::nullopt;
|
|
this->path = std::nullopt;
|
|
this->name = std::string("deleted buffer (" + this->name + ")");
|
|
}
|
|
|
|
|
|
std::string
|
|
Buffer::FileStatusToString(Buffer::FileStatus status)
|
|
{
|
|
switch (status) {
|
|
case FileStatus::FileStatusOK:
|
|
return std::string("OK");
|
|
case FileStatus::FileStatusReadOnly:
|
|
return std::string("read-only file");
|
|
case FileStatus::FileStatusIOFailed:
|
|
return std::string("I/O failure");
|
|
case FileStatus::FileStatusVirtual:
|
|
return std::string("virtual buffer");
|
|
case FileStatus::FileStatusInvalidPermissions:
|
|
return std::string("invalid permissions");
|
|
case FileStatus::FileStatusNonExistent:
|
|
return std::string("file does not exist");
|
|
default:
|
|
abort();
|
|
|
|
}
|
|
}
|
|
|
|
|
|
bool
|
|
Buffer::Exists()
|
|
{
|
|
if (!this->file) {
|
|
return false;
|
|
}
|
|
|
|
return this->file.value().Exists();
|
|
}
|
|
|
|
|
|
|
|
void
|
|
Buffer::PrintBufferStatus(std::ostream &os)
|
|
{
|
|
os << "buffer:<" << this->name << ">[";
|
|
if (this->IsVirtual()) {
|
|
os << "virt]";
|
|
} else {
|
|
os << "file:";
|
|
if (this->Exists()) {
|
|
os << "real";
|
|
} else {
|
|
os << "new";
|
|
}
|
|
os << ":";
|
|
auto &realFile = this->file.value();
|
|
if (realFile.IsReadWriteable()) {
|
|
os << "rw";
|
|
} else if (realFile.IsReadable()) {
|
|
os << "r-";
|
|
} else if (realFile.IsWriteable()) {
|
|
os << "-w";
|
|
} else {
|
|
os << " ";
|
|
}
|
|
|
|
if (realFile.IsReadOnly()) {
|
|
os << "@";
|
|
} else {
|
|
os << "-";
|
|
}
|
|
|
|
if (this->IsDirty()) {
|
|
os << "!";
|
|
} else {
|
|
os << "-";
|
|
}
|
|
|
|
os << ":" << this->Size() << "B";
|
|
}
|
|
|
|
os << "\n";
|
|
return;
|
|
}
|