/* * synergy -- mouse and keyboard sharing utility * Copyright (C) 2012 Bolton Software Ltd. * Copyright (C) 2012 Nick Bolton * * This package is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * found in the file COPYING that should have accompanied this file. * * This package 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, see . */ #include "CIpcLogOutputter.h" #include "CIpcServer.h" #include "CIpcMessage.h" #include "Ipc.h" #include "CEvent.h" #include "CEventQueue.h" #include "TMethodEventJob.h" #include "CIpcClientProxy.h" #include "CArch.h" #include "CThread.h" #include "TMethodJob.h" #include "XArch.h" // limit number of log lines sent in one message. #define MAX_SEND 100 CIpcLogOutputter::CIpcLogOutputter(CIpcServer& ipcServer) : m_ipcServer(ipcServer), m_bufferMutex(ARCH->newMutex()), m_sending(false), m_running(true), m_notifyCond(ARCH->newCondVar()), m_notifyMutex(ARCH->newMutex()), m_bufferWaiting(false) { m_bufferThread = new CThread(new TMethodJob( this, &CIpcLogOutputter::bufferThread)); } CIpcLogOutputter::~CIpcLogOutputter() { m_running = false; notifyBuffer(); m_bufferThread->wait(5); ARCH->closeMutex(m_bufferMutex); delete m_bufferThread; ARCH->closeCondVar(m_notifyCond); ARCH->closeMutex(m_notifyMutex); } void CIpcLogOutputter::open(const char* title) { } void CIpcLogOutputter::close() { } void CIpcLogOutputter::show(bool showIfEmpty) { } bool CIpcLogOutputter::write(ELevel level, const char* text) { return write(level, text, false); } bool CIpcLogOutputter::write(ELevel, const char* text, bool force) { // TODO: discard based on thread id? hmm... // sending the buffer generates log messages, which we must throw // away (otherwise this would cause recursion). this is just a drawback // of logging this way. there is also the risk that this could throw // away log messages not generated by the ipc, but it seems like it // would be difficult to distinguish (other than looking at the stack // trace somehow). perhaps a file stream might be a better option :-/ if (m_sending && !force) { // ignore events from the buffer thread (would cause recursion). if (CThread::getCurrentThread().getID() == m_bufferThreadId) { return true; } } appendBuffer(text); notifyBuffer(); return true; } void CIpcLogOutputter::appendBuffer(const CString& text) { CArchMutexLock lock(m_bufferMutex); m_buffer.push(text); } void CIpcLogOutputter::bufferThread(void*) { CArchMutexLock lock(m_notifyMutex); m_bufferThreadId = m_bufferThread->getID(); try { while (m_running) { if (m_ipcServer.hasClients(kIpcClientGui)) { // buffer is sent in chunks, so keep sending until it's // empty (or the program has stopped in the meantime). while (m_running && !m_buffer.empty()) { sendBuffer(); } } // program may be stopping while we were in the send loop. if (!m_running) { break; } m_bufferWaiting = true; ARCH->waitCondVar(m_notifyCond, m_notifyMutex, -1); m_bufferWaiting = false; } } catch (XArch& e) { LOG((CLOG_ERR "ipc log buffer thread error, %s", e.what().c_str())); } LOG((CLOG_DEBUG "ipc log buffer thread finished")); } void CIpcLogOutputter::notifyBuffer() { if (!m_bufferWaiting) { return; } CArchMutexLock lock(m_notifyMutex); ARCH->broadcastCondVar(m_notifyCond); } CString CIpcLogOutputter::getChunk(size_t count) { CArchMutexLock lock(m_bufferMutex); if (m_buffer.size() < count) { count = m_buffer.size(); } CString chunk; for (size_t i = 0; i < count; i++) { chunk.append(m_buffer.front()); chunk.append("\n"); m_buffer.pop(); } return chunk; } void CIpcLogOutputter::sendBuffer() { CIpcLogLineMessage message(getChunk(MAX_SEND)); m_sending = true; m_ipcServer.send(message, kIpcClientGui); m_sending = false; }