/* * AbiCollab - Code to enable the modification of remote documents. * Copyright (C) 2005 by Martin Sevior * Copyright (C) 2006 by Marc Maurer * Copyright (C) 2007 One Laptop Per Child * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * 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 General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA. */ #include #include #include #include "DiskSessionRecorder.h" DiskSessionRecorder::DiskSessionRecorder(AbiCollab* pSession) : SessionRecorderInterface(pSession) { UT_DEBUGMSG(("DiskSessionRecorder::DiskSessionRecorder()\n")); std::string pidStr; #ifndef _WIN32 pidStr = str(boost::format( "%1%" ) % int(getpid()) ); #endif gchar *s = g_build_filename(getTargetDirectory(), std::string(getPrefix() + pSession->getSessionId()).c_str(), nullptr); std::string fn = (char*)s; fn += "."; fn += pidStr; FREEP(s); FILE* file = fopen( fn.c_str(), "wb" ); if (file) { setbuf( file, NULL ); // disable file caching m_URI = UT_go_filename_to_uri( fn.c_str() ); m_Error = NULL; m_GsfStream = gsf_output_stdio_new_FILE( m_URI, file, FALSE ); if (m_GsfStream) { UT_DEBUGMSG(("DiskSessionRecorder: writing to file %s\n", fn.c_str())); write( getHeader(), strlen(getHeader()) ); UT_sint32 version = ABICOLLAB_PROTOCOL_VERSION; write( &version, sizeof(version) ); char bLocallyControlled = pSession->isLocallyControlled(); write( &bLocallyControlled, sizeof(bLocallyControlled) ); } else { UT_DEBUGMSG(("DiskSessionRecorder: stopping, can't write to file %s\n", fn.c_str())); } } else { m_URI = NULL; m_Error = NULL; m_GsfStream = NULL; } } DiskSessionRecorder::~DiskSessionRecorder() { destroy(); } bool DiskSessionRecorder::getPackets(const std::string& filename, bool& bLocallyControlled, std::vector& packets) { // open file GsfInput* in = UT_go_file_open(filename.c_str(), NULL); UT_return_val_if_fail(in,false); // read contents size_t fileSize = gsf_input_size(in); guint8 const* contents = gsf_input_read(in, fileSize, NULL); if (!contents) { g_object_unref(G_OBJECT(in)); UT_return_val_if_fail(false,false); } // ugly: copy to string, because our serialization code expects a string std::string buffer; buffer.resize( fileSize ); memcpy( &buffer[0], contents, fileSize ); // check header size_t headerLen = strlen( DiskSessionRecorder::getHeader() ); if (memcmp( DiskSessionRecorder::getHeader(), &buffer[0], headerLen )) { UT_DEBUGMSG(("%s does not seem to be a session file\n", filename.c_str() )); UT_return_val_if_fail(false,false); } // check version UT_sint32 version = ABICOLLAB_PROTOCOL_VERSION; if (memcmp( &version, &buffer[headerLen], sizeof(version) )) { UT_DEBUGMSG(("%s has wrong version\n", filename.c_str() )); UT_return_val_if_fail(false,false); } bLocallyControlled = false; memcpy(&bLocallyControlled, &buffer[headerLen + sizeof(version)], sizeof(bLocallyControlled)); UT_DEBUGMSG(("Session is %s\n",bLocallyControlled?"locally controlled":"not locally controlled")); // create stream, and skip header (is non-serialized-data) IStrArchive is( buffer ); is.Skip( headerLen + sizeof(version) + sizeof(bLocallyControlled) ); // keep reading packets try { while (!is.EndOfFile()) { // read data for packet char incoming; is << incoming; char hasBuddy; is << hasBuddy; UT_UTF8String buddyName; if (hasBuddy) { is << buddyName; } UT_uint64 timestamp; is << timestamp; unsigned char packetClass; is << packetClass; // NOTE: we can safely cast to a session packet, as only session packets are recorded // TODO: add a safety check SessionPacket* newPacket = static_cast(Packet::createPacket( (PClassType)packetClass )); if (!newPacket) { UT_DEBUGMSG(("unknown packet class %d\n", packetClass )); UT_return_val_if_fail(false,false); } is << *newPacket; packets.push_back(new RecordedPacket(incoming, hasBuddy, buddyName, timestamp, newPacket)); } return true; } catch(...) { UT_DEBUGMSG(("exception caught during packet serialization\n")); } return false; } bool DiskSessionRecorder::dumpSession(const std::string& filename) { bool bLocallyControlled; std::vector packets; if (DiskSessionRecorder::getPackets(filename, bLocallyControlled, packets)) { UT_DEBUGMSG(("Session is %s\n",bLocallyControlled?"locally controlled":"not locally controlled")); UT_uint32 packetCounter = 0; for (std::vector::const_iterator cit = packets.begin(); cit != packets.end(); cit++) { const RecordedPacket* rp = *cit; // display packet printf("--------------------------------------------------------------------------------\n"); #ifndef _WIN32 // could someone find the equivalent of gmtime_r on win32? (or better: an XP function) time_t t = time_t(rp->m_timestamp); struct tm time; gmtime_r( &t, &time ); printf("@ %04d/%02d/%02d %02d:%02d:%02d\n", 1900+time.tm_year, time.tm_mon, time.tm_mday, time.tm_hour, time.tm_min, time.tm_sec); #endif printf("[%06u] %s packet ", packetCounter++, rp->m_bIncoming?"INCOMING":"OUTGOING" ); printf("%s ", rp->m_bIncoming?"from":"to"); if (rp->m_bHasBuddy) { printf("<%s>", rp->m_buddyName.utf8_str()); } else { printf(""); } printf(" of class %s\n", Packet::getPacketClassname(rp->m_pPacket->getClassType())); printf("--------------------------------------------------------------------------------\n"); printf("%s\n", rp->m_pPacket->toStr().c_str()); printf("--------------------------------------------------------------------------------\n"); DELETEP(rp); } } return true; } void DiskSessionRecorder::destroy() { UT_DEBUGMSG(("DiskSessionRecorder::destroy: closing file 0x%p\n", m_GsfStream)); if (m_GsfStream) { gsf_output_close(m_GsfStream); g_object_unref(G_OBJECT(m_GsfStream)); m_GsfStream = NULL; // necessary? /me is gsf n00b } if (m_URI) { FREEP(m_URI); } } void DiskSessionRecorder::store(bool incoming, const Packet* pPacket, BuddyPtr pBuddy) { UT_DEBUGMSG(("DiskSessionRecorder::store: %d 0x%p %s\n", incoming, pPacket, pBuddy ? pBuddy->getDescriptor().utf8_str() : "none")); UT_return_if_fail(pPacket); UT_return_if_fail(m_GsfStream); OStrArchive os; // store if we're incoming or outgoing char incomingC = incoming; os << incomingC; // store if we had a buddy, and who it was char haveBuddy = pBuddy ? 1 : 0; os << haveBuddy; if (haveBuddy) { // FIXME: this is not guaranteed to be unique; a single author can join a session multiple times UT_UTF8String descr = pBuddy->getDescriptor(); os << descr; } // store timestamp, make it 64-bit value always UT_uint64 timestamp = UT_uint64( time(0) ); os << timestamp; // store packet class unsigned char packetClass = pPacket->getClassType(); os << packetClass; // store packet os << const_cast(*pPacket); // write to file! write(os.getData().c_str(), os.Size()); } void DiskSessionRecorder::write(const void* data, int count) { UT_DEBUGMSG(("DiskSessionRecorder::write: 0x%p %d\n", data,count)); UT_return_if_fail(m_GsfStream); gsf_output_write(m_GsfStream, size_t(count), reinterpret_cast(data)); }