#include #include #include "geners/AbsArchive.hh" #include "geners/IOException.hh" #define GS_STREAM_COPY_BUFFER_SIZE 65536 static void archive_stream_copy(std::istream &in, std::size_t count, std::ostream &out) { if (count) { const std::size_t bufsize = GS_STREAM_COPY_BUFFER_SIZE; char buffer[bufsize]; bool in_fail = in.fail(); bool out_fail = out.fail(); while (count > bufsize && !in_fail && !out_fail) { in.read(buffer, bufsize); in_fail = in.fail(); if (!in_fail) { out.write(buffer, bufsize); out_fail = out.fail(); } count -= bufsize; } if (!in_fail && !out_fail) { in.read(buffer, count); if (!in.fail()) out.write(buffer, count); } } } namespace { class NotWritableRecord : public gs::AbsRecord { public: inline NotWritableRecord(const gs::ClassId& classId, const char* ioPrototype, const char* name, const char* category) : gs::AbsRecord(classId, ioPrototype, name, category) {} private: NotWritableRecord(); inline bool writeData(std::ostream&) const {return false;} }; } namespace gs { AbsArchive::AbsArchive(const char* name) : name_(name ? name : ""), lastItemId_(0), lastItemLength_(0) { } void AbsArchive::addItemToReference(AbsReference& r, const unsigned long long id) const { r.addItemId(id); } unsigned long long AbsArchive::copyItem(const unsigned long long id, AbsArchive* destination, const char* newName, const char* newCategory) { if (!isReadable()) throw std::invalid_argument("In gs::AbsArchive::copyItem: " "origin archive is not readable"); assert(destination); if (this == destination) throw std::invalid_argument("In gs::AbsArchive::copyItem: " "origin and destination archives are the same"); AbsArchive& ar(*destination); if (!ar.isWritable()) throw std::invalid_argument("In gs::AbsArchive::copyItem: " "destination archive is not writable"); CPP11_shared_ptr entry(catalogEntry(id)); if (!entry) throw std::invalid_argument("In gs::AbsArchive::copyItem: no item " "in the archive with the given id"); // Position the input stream long long sz = 0; std::istream& is = inputStream(id, &sz); // The following code is similar to the one in the "operator<<" // below w.r.t. operations with the output archive std::ostream& os = ar.outputStream(); std::streampos base = os.tellp(); std::ostream& compressed = ar.compressedStream(os); // Transfer the data between the streams unsigned long long len; if (sz >= 0) len = sz; else len = entry->itemLength(); archive_stream_copy(is, len, compressed); if (is.fail()) throw IOReadFailure("In gs::AbsArchive::copyItem: " "input stream failure"); if (compressed.fail()) throw IOWriteFailure("In gs::AbsArchive::copyItem: " "output stream failure"); const unsigned compressCode = ar.flushCompressedRecord(compressed); if (os.fail()) throw IOWriteFailure("In gs::AbsArchive::copyItem: " "failed to transfer compressed data"); // Figure out the record length. Naturally, can't have negative length. std::streamoff off = os.tellp() - base; const long long delta = off; assert(delta >= 0LL); // We need to create a record out of the catalog entry we have found const char* name = newName; if (!name) name = entry->name().c_str(); const char* category = newCategory; if (!category) category = entry->category().c_str(); NotWritableRecord record(entry->type(), entry->ioPrototype().c_str(), name, category); // Add the record to the catalog const unsigned long long id2 = ar.addToCatalog( record, compressCode, delta); if (id == 0ULL) throw IOWriteFailure("In gs::AbsArchive::copyItem: " "failed to add catalog entry"); ar.lastItemId_ = id2; ar.lastItemLength_ = delta; return id2; } } static std::string local_error_message(gs::AbsArchive& ar, const gs::AbsRecord& record, const char* failedAction) { std::ostringstream err; err << "In operator<<(gs::AbsArchive& ar, const gs::AbsRecord& record): " << "failed to " << failedAction << " to the archive \"" << ar.name() << "\" for item with type \"" << record.type().name() << "\", name \"" << record.name() << "\", and category \"" << record.category() << '"'; return err.str(); } gs::AbsArchive& operator<<(gs::AbsArchive& ar, const gs::AbsRecord& record) { // Do not reuse records if (record.id()) throw std::invalid_argument( "In operator<<(gs::AbsArchive& ar, const gs::AbsRecord& record): " "records can not be reused"); // Get the relevant streams. Do not change the order // of the next three lines, some code which does // not implement compression may actually rely on // the fact that the "outputStream()" method // is called before "compressedStream()". std::ostream& os = ar.outputStream(); std::streampos base = os.tellp(); std::ostream& compressed = ar.compressedStream(os); // Write the data if (!record.writeData(compressed)) throw gs::IOWriteFailure(local_error_message(ar, record, "write data")); const unsigned compressCode = ar.flushCompressedRecord(compressed); if (os.fail()) throw gs::IOWriteFailure(local_error_message( ar, record, "transfer compressed data")); // Figure out the record length. Naturally, can't have negative length. std::streamoff off = os.tellp() - base; const long long delta = off; assert(delta >= 0LL); // Add the record to the catalog const unsigned long long id = ar.addToCatalog(record, compressCode, delta); if (id == 0ULL) throw gs::IOWriteFailure(local_error_message( ar, record, "add catalog entry")); // Mark record as written and give feedback about item length record.itemId_ = id; record.itemLength_ = delta; // Same thing for the archive ar.lastItemId_ = id; ar.lastItemLength_ = delta; return ar; }