nearly pure re-factor: split out code into its own modules.
StateRecorder.hpp split from ChildSession.cpp KitWebSocketHandler.[ch]pp split from Kit.cpp. ThreadPool.hpp split from RenderTiles.hpp Expose headers for KitSocketPoll and Document at the same time. Not clear we need the DocumentManagerInterface anymore. Conditionally compile out Document::createSession for unittest dependency breaking, and avoid Rlimit::handleSetrlimitCommand likewise. Make makePropertyValue a private method of Kit.cpp. clang-format new files. Change-Id: I47a1d6afe20165f156b477a931b94c916cff4b9d Signed-off-by: Michael Meeks <michael.meeks@collabora.com>pull/8479/head
parent
e59349f1c3
commit
c117d87bb4
|
@ -194,7 +194,8 @@ libsimd_a_CFLAGS = @SIMD_CFLAGS@
|
|||
|
||||
coolforkit_sources = kit/ChildSession.cpp \
|
||||
kit/ForKit.cpp \
|
||||
kit/Kit.cpp
|
||||
kit/Kit.cpp \
|
||||
kit/KitWebSocket.cpp
|
||||
|
||||
coolforkit_SOURCES = $(coolforkit_sources) \
|
||||
$(shared_sources) \
|
||||
|
@ -344,6 +345,7 @@ shared_headers = common/Common.hpp \
|
|||
common/SpookyV2.h \
|
||||
common/CommandControl.hpp \
|
||||
common/Simd.hpp \
|
||||
common/ThreadPool.hpp \
|
||||
net/Buffer.hpp \
|
||||
net/DelaySocket.hpp \
|
||||
net/FakeSocket.hpp \
|
||||
|
@ -366,7 +368,9 @@ kit_headers = kit/ChildSession.hpp \
|
|||
kit/DummyLibreOfficeKit.hpp \
|
||||
kit/Kit.hpp \
|
||||
kit/KitHelper.hpp \
|
||||
kit/KitWebSocket.hpp \
|
||||
kit/SetupKitEnvironment.hpp \
|
||||
kit/StateRecorder.hpp \
|
||||
kit/Watermark.hpp
|
||||
|
||||
noinst_HEADERS = $(wsd_headers) $(shared_headers) $(kit_headers) \
|
||||
|
|
|
@ -21,6 +21,7 @@ add_library(androidapp SHARED
|
|||
../../../../../kit/ChildSession.cpp
|
||||
../../../../../kit/DeltaSimd.c
|
||||
../../../../../kit/Kit.cpp
|
||||
../../../../../kit/KitWebSocket.cpp
|
||||
../../../../../net/FakeSocket.cpp
|
||||
../../../../../net/Socket.cpp
|
||||
../../../../../wsd/ClientSession.cpp
|
||||
|
|
|
@ -20,139 +20,15 @@
|
|||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
#include <LibreOfficeKit/LibreOfficeKit.hxx>
|
||||
|
||||
#include <common/ThreadPool.hpp>
|
||||
|
||||
#include "Png.hpp"
|
||||
#include "Delta.hpp"
|
||||
#include "Rectangle.hpp"
|
||||
#include "TileDesc.hpp"
|
||||
|
||||
class ThreadPool {
|
||||
std::mutex _mutex;
|
||||
std::condition_variable _cond;
|
||||
std::condition_variable _complete;
|
||||
typedef std::function<void()> ThreadFn;
|
||||
std::queue<ThreadFn> _work;
|
||||
std::vector<std::thread> _threads;
|
||||
size_t _working;
|
||||
bool _shutdown;
|
||||
std::atomic<bool> _running;
|
||||
public:
|
||||
ThreadPool()
|
||||
: _working(0),
|
||||
_shutdown(false),
|
||||
_running(false)
|
||||
{
|
||||
int maxConcurrency = 2;
|
||||
#if WASMAPP
|
||||
// Leave it at that.
|
||||
#elif MOBILEAPP && !defined(GTKAPP)
|
||||
maxConcurrency = std::max<int>(std::thread::hardware_concurrency(), 2);
|
||||
#else
|
||||
// coverity[tainted_return_value] - we trust the contents of this variable
|
||||
const char *max = getenv("MAX_CONCURRENCY");
|
||||
if (max)
|
||||
maxConcurrency = atoi(max);
|
||||
#endif
|
||||
LOG_TRC("PNG compression thread pool size " << maxConcurrency);
|
||||
for (int i = 1; i < maxConcurrency; ++i)
|
||||
_threads.push_back(std::thread(&ThreadPool::work, this));
|
||||
}
|
||||
|
||||
~ThreadPool() { stop(); }
|
||||
|
||||
void stop()
|
||||
{
|
||||
{
|
||||
std::unique_lock< std::mutex > lock(_mutex);
|
||||
assert(_working == 0);
|
||||
_shutdown = true;
|
||||
}
|
||||
_cond.notify_all();
|
||||
for (auto& it : _threads)
|
||||
it.join();
|
||||
}
|
||||
|
||||
size_t count() const
|
||||
{
|
||||
return _work.size();
|
||||
}
|
||||
|
||||
void pushWork(const ThreadFn &fn)
|
||||
{
|
||||
std::unique_lock< std::mutex > lock(_mutex);
|
||||
assert(!_running);
|
||||
assert(_working == 0);
|
||||
_work.push(fn);
|
||||
}
|
||||
|
||||
void runOne(std::unique_lock< std::mutex >& lock)
|
||||
{
|
||||
assert(_running);
|
||||
assert(!_work.empty());
|
||||
|
||||
ThreadFn fn = _work.front();
|
||||
_work.pop();
|
||||
_working++;
|
||||
lock.unlock();
|
||||
|
||||
try {
|
||||
fn();
|
||||
} catch(...) {
|
||||
LOG_ERR("Exception in thread pool execution.");
|
||||
}
|
||||
|
||||
lock.lock();
|
||||
_working--;
|
||||
if (_work.empty() && _working == 0)
|
||||
_complete.notify_all();
|
||||
}
|
||||
|
||||
void run()
|
||||
{
|
||||
std::unique_lock< std::mutex > lock(_mutex);
|
||||
assert(!_running);
|
||||
assert(_working == 0);
|
||||
|
||||
_running = true;
|
||||
|
||||
// Avoid notifying threads if we don't need to.
|
||||
bool useThreads = _threads.size() > 1 && _work.size() > 1;
|
||||
if (useThreads)
|
||||
_cond.notify_all();
|
||||
|
||||
while(!_work.empty())
|
||||
runOne(lock);
|
||||
|
||||
if (useThreads && (_working > 0 || !_work.empty()))
|
||||
_complete.wait(lock, [this]() { return _working == 0 && _work.empty(); } );
|
||||
|
||||
_running = false;
|
||||
|
||||
assert(_working==0);
|
||||
assert(_work.empty());
|
||||
}
|
||||
|
||||
void work()
|
||||
{
|
||||
std::unique_lock< std::mutex > lock(_mutex);
|
||||
while (!_shutdown)
|
||||
{
|
||||
_cond.wait(lock);
|
||||
while (!_shutdown && !_work.empty() && _running)
|
||||
runOne(lock);
|
||||
}
|
||||
}
|
||||
|
||||
void dumpState(std::ostream& oss)
|
||||
{
|
||||
oss << "\tthreadPool:"
|
||||
<< "\n\t\tshutdown: " << _shutdown
|
||||
<< "\n\t\tworking: " << _working
|
||||
<< "\n\t\twork count: " << count()
|
||||
<< "\n\t\tthread count " << _threads.size()
|
||||
<< "\n";
|
||||
}
|
||||
};
|
||||
|
||||
namespace RenderTiles
|
||||
{
|
||||
struct Buffer {
|
||||
|
|
|
@ -0,0 +1,148 @@
|
|||
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
|
||||
/*
|
||||
* Copyright the Collabora Online contributors.
|
||||
*
|
||||
* SPDX-License-Identifier: MPL-2.0
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cassert>
|
||||
#include <memory>
|
||||
#include <queue>
|
||||
#include <thread>
|
||||
#include <condition_variable>
|
||||
#include <fstream>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
class ThreadPool
|
||||
{
|
||||
std::mutex _mutex;
|
||||
std::condition_variable _cond;
|
||||
std::condition_variable _complete;
|
||||
typedef std::function<void()> ThreadFn;
|
||||
std::queue<ThreadFn> _work;
|
||||
std::vector<std::thread> _threads;
|
||||
size_t _working;
|
||||
bool _shutdown;
|
||||
std::atomic<bool> _running;
|
||||
|
||||
public:
|
||||
ThreadPool()
|
||||
: _working(0)
|
||||
, _shutdown(false)
|
||||
, _running(false)
|
||||
{
|
||||
int maxConcurrency = 2;
|
||||
#if WASMAPP
|
||||
// Leave it at that.
|
||||
#elif MOBILEAPP && !defined(GTKAPP)
|
||||
maxConcurrency = std::max<int>(std::thread::hardware_concurrency(), 2);
|
||||
#else
|
||||
// coverity[tainted_return_value] - we trust the contents of this variable
|
||||
const char* max = getenv("MAX_CONCURRENCY");
|
||||
if (max)
|
||||
maxConcurrency = atoi(max);
|
||||
#endif
|
||||
LOG_TRC("PNG compression thread pool size " << maxConcurrency);
|
||||
for (int i = 1; i < maxConcurrency; ++i)
|
||||
_threads.push_back(std::thread(&ThreadPool::work, this));
|
||||
}
|
||||
|
||||
~ThreadPool() { stop(); }
|
||||
|
||||
void stop()
|
||||
{
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(_mutex);
|
||||
assert(_working == 0);
|
||||
_shutdown = true;
|
||||
}
|
||||
_cond.notify_all();
|
||||
for (auto& it : _threads)
|
||||
it.join();
|
||||
}
|
||||
|
||||
size_t count() const { return _work.size(); }
|
||||
|
||||
void pushWork(const ThreadFn& fn)
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(_mutex);
|
||||
assert(!_running);
|
||||
assert(_working == 0);
|
||||
_work.push(fn);
|
||||
}
|
||||
|
||||
void runOne(std::unique_lock<std::mutex>& lock)
|
||||
{
|
||||
assert(_running);
|
||||
assert(!_work.empty());
|
||||
|
||||
ThreadFn fn = _work.front();
|
||||
_work.pop();
|
||||
_working++;
|
||||
lock.unlock();
|
||||
|
||||
try
|
||||
{
|
||||
fn();
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
LOG_ERR("Exception in thread pool execution.");
|
||||
}
|
||||
|
||||
lock.lock();
|
||||
_working--;
|
||||
if (_work.empty() && _working == 0)
|
||||
_complete.notify_all();
|
||||
}
|
||||
|
||||
void run()
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(_mutex);
|
||||
assert(!_running);
|
||||
assert(_working == 0);
|
||||
|
||||
_running = true;
|
||||
|
||||
// Avoid notifying threads if we don't need to.
|
||||
bool useThreads = _threads.size() > 1 && _work.size() > 1;
|
||||
if (useThreads)
|
||||
_cond.notify_all();
|
||||
|
||||
while (!_work.empty())
|
||||
runOne(lock);
|
||||
|
||||
if (useThreads && (_working > 0 || !_work.empty()))
|
||||
_complete.wait(lock, [this]() { return _working == 0 && _work.empty(); });
|
||||
|
||||
_running = false;
|
||||
|
||||
assert(_working == 0);
|
||||
assert(_work.empty());
|
||||
}
|
||||
|
||||
void work()
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(_mutex);
|
||||
while (!_shutdown)
|
||||
{
|
||||
_cond.wait(lock);
|
||||
while (!_shutdown && !_work.empty() && _running)
|
||||
runOne(lock);
|
||||
}
|
||||
}
|
||||
|
||||
void dumpState(std::ostream& oss)
|
||||
{
|
||||
oss << "\tthreadPool:"
|
||||
<< "\n\t\tshutdown: " << _shutdown << "\n\t\tworking: " << _working
|
||||
<< "\n\t\twork count: " << count() << "\n\t\tthread count " << _threads.size() << "\n";
|
||||
}
|
||||
};
|
|
@ -34,7 +34,8 @@ common_sources = \
|
|||
../common/Util.cpp
|
||||
|
||||
kit_sources = ../kit/ChildSession.cpp \
|
||||
../kit/Kit.cpp
|
||||
../kit/Kit.cpp \
|
||||
../kit/KitWebSocket.cpp
|
||||
|
||||
net_sources = ../net/FakeSocket.cpp \
|
||||
../net/Socket.cpp
|
||||
|
|
|
@ -23,6 +23,7 @@
|
|||
#include "Kit.hpp"
|
||||
#include "Session.hpp"
|
||||
#include "Watermark.hpp"
|
||||
#include "StateRecorder.hpp"
|
||||
|
||||
class ChildSession;
|
||||
|
||||
|
@ -32,181 +33,7 @@ enum class LokEventTargetEnum
|
|||
Window
|
||||
};
|
||||
|
||||
// An abstract interface.
|
||||
class DocumentManagerInterface
|
||||
{
|
||||
public:
|
||||
virtual ~DocumentManagerInterface() {}
|
||||
|
||||
/// Request loading a document, or a new view, if one exists.
|
||||
virtual bool onLoad(const std::string& sessionId,
|
||||
const std::string& uriAnonym,
|
||||
const std::string& renderOpts) = 0;
|
||||
|
||||
/// Unload a client session, which unloads the document
|
||||
/// if it is the last and only.
|
||||
virtual void onUnload(const ChildSession& session) = 0;
|
||||
|
||||
/// Access to the Kit instance.
|
||||
virtual std::shared_ptr<lok::Office> getLOKit() = 0;
|
||||
|
||||
/// Access to the document instance.
|
||||
virtual std::shared_ptr<lok::Document> getLOKitDocument() = 0;
|
||||
|
||||
/// Send msg to all active sessions.
|
||||
virtual bool notifyAll(const std::string& msg) = 0;
|
||||
|
||||
/// Send updated view info to all active sessions.
|
||||
virtual void notifyViewInfo() = 0;
|
||||
virtual void updateEditorSpeeds(int id, int speed) = 0;
|
||||
|
||||
virtual int getEditorId() const = 0;
|
||||
|
||||
/// Get a view ID <-> UserInfo map.
|
||||
virtual std::map<int, UserInfo> getViewInfo() = 0;
|
||||
|
||||
virtual std::string getObfuscatedFileId() = 0;
|
||||
|
||||
virtual std::shared_ptr<TileQueue>& getTileQueue() = 0;
|
||||
|
||||
virtual bool sendFrame(const char* buffer, int length, WSOpCode opCode = WSOpCode::Text) = 0;
|
||||
|
||||
virtual void alertAllUsers(const std::string& cmd, const std::string& kind) = 0;
|
||||
|
||||
virtual unsigned getMobileAppDocId() const = 0;
|
||||
|
||||
/// See if we should clear out our memory
|
||||
virtual void trimIfInactive() = 0;
|
||||
|
||||
virtual bool isDocPasswordProtected() const = 0;
|
||||
|
||||
virtual bool haveDocPassword() const = 0;
|
||||
|
||||
virtual std::string getDocPassword() const = 0;
|
||||
|
||||
virtual DocumentPasswordType getDocPasswordType() const = 0;
|
||||
|
||||
virtual void updateActivityHeader() const = 0;
|
||||
};
|
||||
|
||||
struct RecordedEvent
|
||||
{
|
||||
private:
|
||||
int _type = 0;
|
||||
std::string _payload;
|
||||
|
||||
public:
|
||||
RecordedEvent()
|
||||
{
|
||||
}
|
||||
|
||||
RecordedEvent(int type, const std::string& payload)
|
||||
: _type(type),
|
||||
_payload(payload)
|
||||
{
|
||||
}
|
||||
|
||||
void setType(int type)
|
||||
{
|
||||
_type = type;
|
||||
}
|
||||
|
||||
int getType() const
|
||||
{
|
||||
return _type;
|
||||
}
|
||||
|
||||
void setPayload(const std::string& payload)
|
||||
{
|
||||
_payload = payload;
|
||||
}
|
||||
|
||||
const std::string& getPayload() const
|
||||
{
|
||||
return _payload;
|
||||
}
|
||||
};
|
||||
|
||||
/// When the session is inactive, we need to record its state for a replay.
|
||||
class StateRecorder
|
||||
{
|
||||
private:
|
||||
bool _invalidate;
|
||||
std::unordered_map<std::string, std::string> _recordedStates;
|
||||
std::unordered_map<int, std::unordered_map<int, RecordedEvent>> _recordedViewEvents;
|
||||
std::unordered_map<int, RecordedEvent> _recordedEvents;
|
||||
std::vector<RecordedEvent> _recordedEventsVector;
|
||||
|
||||
public:
|
||||
StateRecorder() : _invalidate(false) {}
|
||||
|
||||
// TODO Remember the maximal area we need to invalidate - grow it step by step.
|
||||
void recordInvalidate()
|
||||
{
|
||||
_invalidate = true;
|
||||
}
|
||||
|
||||
bool isInvalidate() const
|
||||
{
|
||||
return _invalidate;
|
||||
}
|
||||
|
||||
const std::unordered_map<std::string, std::string>& getRecordedStates() const
|
||||
{
|
||||
return _recordedStates;
|
||||
}
|
||||
|
||||
const std::unordered_map<int, std::unordered_map<int, RecordedEvent>>& getRecordedViewEvents() const
|
||||
{
|
||||
return _recordedViewEvents;
|
||||
}
|
||||
|
||||
const std::unordered_map<int, RecordedEvent>& getRecordedEvents() const
|
||||
{
|
||||
return _recordedEvents;
|
||||
}
|
||||
|
||||
const std::vector<RecordedEvent>& getRecordedEventsVector() const
|
||||
{
|
||||
return _recordedEventsVector;
|
||||
}
|
||||
|
||||
void recordEvent(const int type, const std::string& payload)
|
||||
{
|
||||
_recordedEvents[type] = RecordedEvent(type, payload);
|
||||
}
|
||||
|
||||
void recordViewEvent(const int viewId, const int type, const std::string& payload)
|
||||
{
|
||||
_recordedViewEvents[viewId][type] = {type, payload};
|
||||
}
|
||||
|
||||
void recordState(const std::string& name, const std::string& value)
|
||||
{
|
||||
_recordedStates[name] = value;
|
||||
}
|
||||
|
||||
/// In the case we need to remember all the events that come, not just
|
||||
/// the final state.
|
||||
void recordEventSequence(const int type, const std::string& payload)
|
||||
{
|
||||
_recordedEventsVector.emplace_back(type, payload);
|
||||
}
|
||||
|
||||
void dumpState(std::ostream&)
|
||||
{
|
||||
// TODO: the rest ...
|
||||
}
|
||||
|
||||
void clear()
|
||||
{
|
||||
_invalidate = false;
|
||||
_recordedEvents.clear();
|
||||
_recordedViewEvents.clear();
|
||||
_recordedStates.clear();
|
||||
_recordedEventsVector.clear();
|
||||
}
|
||||
};
|
||||
class DocumentManagerInterface;
|
||||
|
||||
/// Represents a session to the WSD process, in a Kit process. Note that this is not a singleton.
|
||||
class ChildSession final : public Session
|
||||
|
|
|
@ -32,6 +32,7 @@
|
|||
#include <chrono>
|
||||
|
||||
#include <Poco/Path.h>
|
||||
#include <Poco/URI.h>
|
||||
|
||||
#include <Common.hpp>
|
||||
#include "Kit.hpp"
|
||||
|
@ -41,9 +42,6 @@
|
|||
#include <Unit.hpp>
|
||||
#include <Util.hpp>
|
||||
#include <WebSocketHandler.hpp>
|
||||
#if !MOBILEAPP
|
||||
#include <Admin.hpp>
|
||||
#endif
|
||||
|
||||
#include <common/FileUtil.hpp>
|
||||
#include <common/JailUtil.hpp>
|
||||
|
|
3175
kit/Kit.cpp
3175
kit/Kit.cpp
File diff suppressed because it is too large
Load Diff
406
kit/Kit.hpp
406
kit/Kit.hpp
|
@ -16,7 +16,11 @@
|
|||
#include <string>
|
||||
|
||||
#include <common/Util.hpp>
|
||||
#include <common/Session.hpp>
|
||||
#include <common/ThreadPool.hpp>
|
||||
#include <wsd/TileDesc.hpp>
|
||||
|
||||
#include "Delta.hpp"
|
||||
#include "Socket.hpp"
|
||||
|
||||
#define LOK_USE_UNSTABLE_API
|
||||
|
@ -31,20 +35,13 @@
|
|||
|
||||
void lokit_main(
|
||||
#if !MOBILEAPP
|
||||
const std::string& childRoot,
|
||||
const std::string& jailId,
|
||||
const std::string& sysTemplate,
|
||||
const std::string& loTemplate,
|
||||
bool noCapabilities,
|
||||
bool noSeccomp,
|
||||
bool queryVersionInfo,
|
||||
bool displayVersion,
|
||||
const std::string& childRoot, const std::string& jailId, const std::string& sysTemplate,
|
||||
const std::string& loTemplate, bool noCapabilities, bool noSeccomp, bool queryVersionInfo,
|
||||
bool displayVersion,
|
||||
#else
|
||||
int docBrokerSocket,
|
||||
const std::string& userInterface,
|
||||
int docBrokerSocket, const std::string& userInterface,
|
||||
#endif
|
||||
std::size_t numericIdentifier
|
||||
);
|
||||
std::size_t numericIdentifier);
|
||||
|
||||
#ifdef IOS
|
||||
void runKitLoopInAThread();
|
||||
|
@ -60,22 +57,15 @@ class DocumentManagerInterface;
|
|||
/// callback to a specific view.
|
||||
struct CallbackDescriptor
|
||||
{
|
||||
CallbackDescriptor(DocumentManagerInterface* const doc,
|
||||
const int viewId) :
|
||||
_doc(doc),
|
||||
_viewId(viewId)
|
||||
CallbackDescriptor(DocumentManagerInterface* const doc, const int viewId)
|
||||
: _doc(doc)
|
||||
, _viewId(viewId)
|
||||
{
|
||||
}
|
||||
|
||||
DocumentManagerInterface* getDoc() const
|
||||
{
|
||||
return _doc;
|
||||
}
|
||||
DocumentManagerInterface* getDoc() const { return _doc; }
|
||||
|
||||
int getViewId() const
|
||||
{
|
||||
return _viewId;
|
||||
}
|
||||
int getViewId() const { return _viewId; }
|
||||
|
||||
private:
|
||||
DocumentManagerInterface* const _doc;
|
||||
|
@ -92,43 +82,25 @@ struct UserInfo
|
|||
{
|
||||
}
|
||||
|
||||
UserInfo(const std::string& userId,
|
||||
const std::string& userName,
|
||||
const std::string& userExtraInfo,
|
||||
const std::string& userPrivateInfo,
|
||||
bool readOnly) :
|
||||
_userId(userId),
|
||||
_userName(userName),
|
||||
_userExtraInfo(userExtraInfo),
|
||||
_userPrivateInfo(userPrivateInfo),
|
||||
_readOnly(readOnly)
|
||||
UserInfo(const std::string& userId, const std::string& userName,
|
||||
const std::string& userExtraInfo, const std::string& userPrivateInfo, bool readOnly)
|
||||
: _userId(userId)
|
||||
, _userName(userName)
|
||||
, _userExtraInfo(userExtraInfo)
|
||||
, _userPrivateInfo(userPrivateInfo)
|
||||
, _readOnly(readOnly)
|
||||
{
|
||||
}
|
||||
|
||||
const std::string& getUserId() const
|
||||
{
|
||||
return _userId;
|
||||
}
|
||||
const std::string& getUserId() const { return _userId; }
|
||||
|
||||
const std::string& getUserName() const
|
||||
{
|
||||
return _userName;
|
||||
}
|
||||
const std::string& getUserName() const { return _userName; }
|
||||
|
||||
const std::string& getUserExtraInfo() const
|
||||
{
|
||||
return _userExtraInfo;
|
||||
}
|
||||
const std::string& getUserExtraInfo() const { return _userExtraInfo; }
|
||||
|
||||
const std::string& getUserPrivateInfo() const
|
||||
{
|
||||
return _userPrivateInfo;
|
||||
}
|
||||
const std::string& getUserPrivateInfo() const { return _userPrivateInfo; }
|
||||
|
||||
bool isReadOnly() const
|
||||
{
|
||||
return _readOnly;
|
||||
}
|
||||
bool isReadOnly() const { return _readOnly; }
|
||||
|
||||
private:
|
||||
std::string _userId;
|
||||
|
@ -138,17 +110,332 @@ private:
|
|||
bool _readOnly;
|
||||
};
|
||||
|
||||
|
||||
/// We have two types of password protected documents
|
||||
/// 1) Documents which require password to view
|
||||
/// 2) Document which require password to modify
|
||||
enum class DocumentPasswordType { ToView, ToModify };
|
||||
enum class DocumentPasswordType
|
||||
{
|
||||
ToView,
|
||||
ToModify
|
||||
};
|
||||
|
||||
/// Check the ForkCounter, and if non-zero, fork more of them accordingly.
|
||||
void forkLibreOfficeKit(const std::string& childRoot,
|
||||
const std::string& sysTemplate,
|
||||
void forkLibreOfficeKit(const std::string& childRoot, const std::string& sysTemplate,
|
||||
const std::string& loTemplate);
|
||||
|
||||
class Document;
|
||||
|
||||
/// The main main-loop of the Kit process
|
||||
class KitSocketPoll final : public SocketPoll
|
||||
{
|
||||
std::chrono::steady_clock::time_point _pollEnd;
|
||||
std::shared_ptr<Document> _document;
|
||||
|
||||
static KitSocketPoll* mainPoll;
|
||||
|
||||
KitSocketPoll();
|
||||
|
||||
public:
|
||||
~KitSocketPoll();
|
||||
void drainQueue();
|
||||
|
||||
static void dumpGlobalState(std::ostream& oss);
|
||||
static std::shared_ptr<KitSocketPoll> create();
|
||||
|
||||
virtual void wakeupHook() override;
|
||||
|
||||
#if ENABLE_DEBUG
|
||||
struct ReEntrancyGuard
|
||||
{
|
||||
std::atomic<int>& _count;
|
||||
ReEntrancyGuard(std::atomic<int>& count)
|
||||
: _count(count)
|
||||
{
|
||||
count++;
|
||||
}
|
||||
~ReEntrancyGuard() { _count--; }
|
||||
};
|
||||
#endif
|
||||
int kitPoll(int timeoutMicroS);
|
||||
void setDocument(std::shared_ptr<Document> document) { _document = std::move(document); }
|
||||
|
||||
// unusual LOK event from another thread, push into our loop to process.
|
||||
static bool pushToMainThread(LibreOfficeKitCallback callback, int type, const char* p,
|
||||
void* data);
|
||||
|
||||
#ifdef IOS
|
||||
static std::mutex KSPollsMutex;
|
||||
// static std::condition_variable KSPollsCV;
|
||||
static std::vector<std::weak_ptr<KitSocketPoll>> KSPolls;
|
||||
|
||||
std::mutex terminationMutex;
|
||||
std::condition_variable terminationCV;
|
||||
bool terminationFlag;
|
||||
#endif
|
||||
};
|
||||
|
||||
class TileQueue;
|
||||
class ChildSession;
|
||||
|
||||
// An abstract interface.
|
||||
class DocumentManagerInterface
|
||||
{
|
||||
public:
|
||||
virtual ~DocumentManagerInterface() {}
|
||||
|
||||
/// Request loading a document, or a new view, if one exists.
|
||||
virtual bool onLoad(const std::string& sessionId, const std::string& uriAnonym,
|
||||
const std::string& renderOpts) = 0;
|
||||
|
||||
/// Unload a client session, which unloads the document
|
||||
/// if it is the last and only.
|
||||
virtual void onUnload(const ChildSession& session) = 0;
|
||||
|
||||
/// Access to the Kit instance.
|
||||
virtual std::shared_ptr<lok::Office> getLOKit() = 0;
|
||||
|
||||
/// Access to the document instance.
|
||||
virtual std::shared_ptr<lok::Document> getLOKitDocument() = 0;
|
||||
|
||||
/// Send msg to all active sessions.
|
||||
virtual bool notifyAll(const std::string& msg) = 0;
|
||||
|
||||
/// Send updated view info to all active sessions.
|
||||
virtual void notifyViewInfo() = 0;
|
||||
virtual void updateEditorSpeeds(int id, int speed) = 0;
|
||||
|
||||
virtual int getEditorId() const = 0;
|
||||
|
||||
/// Get a view ID <-> UserInfo map.
|
||||
virtual std::map<int, UserInfo> getViewInfo() = 0;
|
||||
|
||||
virtual std::string getObfuscatedFileId() = 0;
|
||||
|
||||
virtual std::shared_ptr<TileQueue>& getTileQueue() = 0;
|
||||
|
||||
virtual bool sendFrame(const char* buffer, int length, WSOpCode opCode = WSOpCode::Text) = 0;
|
||||
|
||||
virtual void alertAllUsers(const std::string& cmd, const std::string& kind) = 0;
|
||||
|
||||
virtual unsigned getMobileAppDocId() const = 0;
|
||||
|
||||
/// See if we should clear out our memory
|
||||
virtual void trimIfInactive() = 0;
|
||||
|
||||
virtual bool isDocPasswordProtected() const = 0;
|
||||
|
||||
virtual bool haveDocPassword() const = 0;
|
||||
|
||||
virtual std::string getDocPassword() const = 0;
|
||||
|
||||
virtual DocumentPasswordType getDocPasswordType() const = 0;
|
||||
|
||||
virtual void updateActivityHeader() const = 0;
|
||||
};
|
||||
|
||||
/// A document container.
|
||||
/// Owns LOKitDocument instance and connections.
|
||||
/// Manages the lifetime of a document.
|
||||
/// Technically, we can host multiple documents
|
||||
/// per process. But for security reasons don't.
|
||||
/// However, we could have a coolkit instance
|
||||
/// per user or group of users (a trusted circle).
|
||||
class Document final : public DocumentManagerInterface
|
||||
{
|
||||
public:
|
||||
Document(const std::shared_ptr<lok::Office>& loKit, const std::string& jailId,
|
||||
const std::string& docKey, const std::string& docId, const std::string& url,
|
||||
std::shared_ptr<TileQueue> tileQueue,
|
||||
const std::shared_ptr<WebSocketHandler>& websocketHandler, unsigned mobileAppDocId);
|
||||
virtual ~Document();
|
||||
|
||||
const std::string& getUrl() const { return _url; }
|
||||
|
||||
/// Post the message - in the unipoll world we're in the right thread anyway
|
||||
bool postMessage(const char* data, int size, const WSOpCode code) const;
|
||||
|
||||
bool createSession(const std::string& sessionId);
|
||||
|
||||
/// Purges dead connections and returns
|
||||
/// the remaining number of clients.
|
||||
/// Returns -1 on failure.
|
||||
std::size_t purgeSessions();
|
||||
|
||||
void setDocumentPassword(int passwordType);
|
||||
|
||||
void renderTiles(TileCombined& tileCombined);
|
||||
|
||||
bool sendTextFrame(const std::string& message)
|
||||
{
|
||||
return sendFrame(message.data(), message.size());
|
||||
}
|
||||
|
||||
bool sendFrame(const char* buffer, int length, WSOpCode opCode = WSOpCode::Text) override;
|
||||
|
||||
void alertNotAsync()
|
||||
{
|
||||
// load unfortunately enables inputprocessing in some cases.
|
||||
if (processInputEnabled() && !_duringLoad)
|
||||
notifyAll("error: cmd=notasync kind=failure");
|
||||
}
|
||||
|
||||
void alertAllUsers(const std::string& cmd, const std::string& kind) override
|
||||
{
|
||||
alertAllUsers("errortoall: cmd=" + cmd + " kind=" + kind);
|
||||
}
|
||||
|
||||
void alertAllUsers(const std::string& msg) { sendTextFrame(msg); }
|
||||
|
||||
/// Notify all views with the given message
|
||||
bool notifyAll(const std::string& msg) override
|
||||
{
|
||||
// Broadcast updated viewinfo to all clients.
|
||||
return sendTextFrame("client-all " + msg);
|
||||
}
|
||||
|
||||
unsigned getMobileAppDocId() const override { return _mobileAppDocId; }
|
||||
|
||||
void trimIfInactive() override;
|
||||
void trimAfterInactivity();
|
||||
|
||||
// LibreOfficeKit callback entry points
|
||||
static void GlobalCallback(const int type, const char* p, void* data);
|
||||
static void ViewCallback(const int type, const char* p, void* data);
|
||||
|
||||
private:
|
||||
/// Helper method to broadcast callback and its payload to all clients
|
||||
void broadcastCallbackToClients(const int type, const std::string& payload)
|
||||
{
|
||||
_tileQueue->put("callback all " + std::to_string(type) + ' ' + payload);
|
||||
}
|
||||
|
||||
/// Load a document (or view) and register callbacks.
|
||||
bool onLoad(const std::string& sessionId, const std::string& uriAnonym,
|
||||
const std::string& renderOpts) override;
|
||||
void onUnload(const ChildSession& session) override;
|
||||
|
||||
std::map<int, UserInfo> getViewInfo() override { return _sessionUserInfo; }
|
||||
|
||||
std::shared_ptr<TileQueue>& getTileQueue() override { return _tileQueue; }
|
||||
|
||||
int getEditorId() const override { return _editorId; }
|
||||
|
||||
bool isDocPasswordProtected() const override { return _isDocPasswordProtected; }
|
||||
|
||||
bool haveDocPassword() const override { return _haveDocPassword; }
|
||||
|
||||
std::string getDocPassword() const override { return _docPassword; }
|
||||
|
||||
DocumentPasswordType getDocPasswordType() const override { return _docPasswordType; }
|
||||
|
||||
void updateActivityHeader() const override;
|
||||
|
||||
/// Notify all views of viewId and their associated usernames
|
||||
void notifyViewInfo() override;
|
||||
|
||||
std::shared_ptr<ChildSession> findSessionByViewId(int viewId);
|
||||
|
||||
void invalidateCanonicalId(const std::string& sessionId);
|
||||
|
||||
std::string getViewProps(const std::shared_ptr<ChildSession>& session);
|
||||
|
||||
void updateEditorSpeeds(int id, int speed) override;
|
||||
|
||||
private:
|
||||
// Get the color value for all author names from the core
|
||||
std::map<std::string, int> getViewColors();
|
||||
|
||||
std::string getDefaultTheme(const std::shared_ptr<ChildSession>& session) const;
|
||||
|
||||
std::shared_ptr<lok::Document> load(const std::shared_ptr<ChildSession>& session,
|
||||
const std::string& renderOpts);
|
||||
|
||||
bool forwardToChild(const std::string& prefix, const std::vector<char>& payload);
|
||||
|
||||
static std::string makeRenderParams(const std::string& renderOpts, const std::string& userName,
|
||||
const std::string& spellOnline, const std::string& theme);
|
||||
bool isTileRequestInsideVisibleArea(const TileCombined& tileCombined);
|
||||
|
||||
public:
|
||||
void enableProcessInput(bool enable = true) { _inputProcessingEnabled = enable; }
|
||||
bool processInputEnabled() const { return _inputProcessingEnabled; }
|
||||
bool hasQueueItems() const { return _tileQueue && !_tileQueue->isEmpty(); }
|
||||
|
||||
// poll is idle, are we ?
|
||||
void checkIdle();
|
||||
void drainQueue();
|
||||
|
||||
void dumpState(std::ostream& oss);
|
||||
|
||||
private:
|
||||
/// Return access to the lok::Office instance.
|
||||
std::shared_ptr<lok::Office> getLOKit() override { return _loKit; }
|
||||
|
||||
/// Return access to the lok::Document instance.
|
||||
std::shared_ptr<lok::Document> getLOKitDocument() override;
|
||||
|
||||
std::string getObfuscatedFileId() override { return _obfuscatedFileId; }
|
||||
|
||||
#if !MOBILEAPP
|
||||
/// Stops theads, flushes buffers, and exits the process.
|
||||
void flushAndExit(int code);
|
||||
#endif
|
||||
|
||||
private:
|
||||
std::shared_ptr<lok::Office> _loKit;
|
||||
const std::string _jailId;
|
||||
/// URL-based key. May be repeated during the lifetime of WSD.
|
||||
const std::string _docKey;
|
||||
/// Short numerical ID. Unique during the lifetime of WSD.
|
||||
const std::string _docId;
|
||||
const std::string _url;
|
||||
const std::string _obfuscatedFileId;
|
||||
std::string _jailedUrl;
|
||||
std::string _renderOpts;
|
||||
|
||||
std::shared_ptr<lok::Document> _loKitDocument;
|
||||
#ifdef __ANDROID__
|
||||
static std::shared_ptr<lok::Document> _loKitDocumentForAndroidOnly;
|
||||
#endif
|
||||
std::shared_ptr<TileQueue> _tileQueue;
|
||||
std::shared_ptr<WebSocketHandler> _websocketHandler;
|
||||
|
||||
// Document password provided
|
||||
std::string _docPassword;
|
||||
// Whether password was provided or not
|
||||
bool _haveDocPassword;
|
||||
// Whether document is password protected
|
||||
bool _isDocPasswordProtected;
|
||||
// Whether password is required to view the document, or modify it
|
||||
DocumentPasswordType _docPasswordType;
|
||||
|
||||
std::atomic<bool> _stop;
|
||||
|
||||
ThreadPool _pngPool;
|
||||
DeltaGenerator _deltaGen;
|
||||
|
||||
std::condition_variable _cvLoading;
|
||||
int _editorId;
|
||||
bool _editorChangeWarning;
|
||||
std::map<int, std::unique_ptr<CallbackDescriptor>> _viewIdToCallbackDescr;
|
||||
SessionMap<ChildSession> _sessions;
|
||||
|
||||
/// The timestamp of the last memory trimming.
|
||||
std::chrono::steady_clock::time_point _lastMemTrimTime;
|
||||
|
||||
std::map<int, std::chrono::steady_clock::time_point> _lastUpdatedAt;
|
||||
std::map<int, int> _speedCount;
|
||||
/// For showing disconnected user info in the doc repair dialog.
|
||||
std::map<int, UserInfo> _sessionUserInfo;
|
||||
#ifdef __ANDROID__
|
||||
friend std::shared_ptr<lok::Document> getLOKDocumentForAndroidOnly();
|
||||
#endif
|
||||
|
||||
const unsigned _mobileAppDocId;
|
||||
bool _inputProcessingEnabled;
|
||||
int _duringLoad;
|
||||
};
|
||||
|
||||
/// main function of the forkit process or thread
|
||||
int forkit_main(int argc, char** argv);
|
||||
|
||||
|
@ -177,4 +464,7 @@ bool isURPEnabled();
|
|||
/// Start a URP connection, checking if URP is enabled and there is not already an active URP session
|
||||
bool startURP(std::shared_ptr<lok::Office> LOKit, void** ppURPContext);
|
||||
|
||||
/// Ensure all recorded traces hit the disk
|
||||
void flushTraceEventRecordings();
|
||||
|
||||
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
|
||||
|
|
|
@ -0,0 +1,183 @@
|
|||
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
|
||||
/*
|
||||
* Copyright the Collabora Online contributors.
|
||||
*
|
||||
* SPDX-License-Identifier: MPL-2.0
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
/*
|
||||
* The main entry point for the LibreOfficeKit process serving
|
||||
* a document editing session.
|
||||
*/
|
||||
|
||||
#include <config.h>
|
||||
|
||||
#include <Poco/URI.h>
|
||||
#include <sysexits.h> // EX_OK
|
||||
|
||||
#include <common/Seccomp.hpp>
|
||||
#include <common/TraceEvent.hpp>
|
||||
#include <common/MessageQueue.hpp>
|
||||
|
||||
#include <Kit.hpp>
|
||||
#include "KitWebSocket.hpp"
|
||||
|
||||
using Poco::Exception;
|
||||
using Poco::URI;
|
||||
|
||||
void KitWebSocketHandler::handleMessage(const std::vector<char>& data)
|
||||
{
|
||||
// To get A LOT of Trace Events, to exercise their handling, uncomment this:
|
||||
// ProfileZone profileZone("KitWebSocketHandler::handleMessage");
|
||||
|
||||
std::string message(data.data(), data.size());
|
||||
|
||||
#if !MOBILEAPP
|
||||
if (UnitKit::get().filterKitMessage(this, message))
|
||||
return;
|
||||
#endif
|
||||
StringVector tokens = StringVector::tokenize(message);
|
||||
|
||||
LOG_DBG(_socketName << ": recv [" << [&](auto& log) {
|
||||
for (const auto& token : tokens)
|
||||
{
|
||||
// Don't log user-data, there are anonymized versions that get logged instead.
|
||||
if (tokens.startsWith(token, "jail") || tokens.startsWith(token, "author") ||
|
||||
tokens.startsWith(token, "name") || tokens.startsWith(token, "url"))
|
||||
continue;
|
||||
|
||||
log << tokens.getParam(token) << ' ';
|
||||
}
|
||||
});
|
||||
|
||||
// Note: Syntax or parsing errors here are unexpected and fatal.
|
||||
if (SigUtil::getTerminationFlag())
|
||||
{
|
||||
LOG_DBG("Too late, TerminationFlag is set, we're going down");
|
||||
}
|
||||
else if (tokens.equals(0, "session"))
|
||||
{
|
||||
const std::string& sessionId = tokens[1];
|
||||
_docKey = tokens[2];
|
||||
const std::string& docId = tokens[3];
|
||||
const std::string fileId = Util::getFilenameFromURL(_docKey);
|
||||
Util::mapAnonymized(fileId, fileId); // Identity mapping, since fileId is already obfuscated
|
||||
|
||||
std::string url;
|
||||
URI::decode(_docKey, url);
|
||||
#ifndef IOS
|
||||
Util::setThreadName("kit" SHARED_DOC_THREADNAME_SUFFIX + docId);
|
||||
#endif
|
||||
if (!_document)
|
||||
{
|
||||
_document = std::make_shared<Document>(
|
||||
_loKit, _jailId, _docKey, docId, url, _queue,
|
||||
std::static_pointer_cast<WebSocketHandler>(shared_from_this()), _mobileAppDocId);
|
||||
_ksPoll->setDocument(_document);
|
||||
|
||||
// We need to send the process name information to WSD if Trace Event recording is enabled (but
|
||||
// not turned on) because it might be turned on later.
|
||||
// We can do this only after creating the Document object.
|
||||
TraceEvent::emitOneRecordingIfEnabled(
|
||||
std::string("{\"name\":\"process_name\",\"ph\":\"M\",\"args\":{\"name\":\"") +
|
||||
"Kit-" + docId + "\"},\"pid\":" + std::to_string(getpid()) +
|
||||
",\"tid\":" + std::to_string(Util::getThreadId()) + "},\n");
|
||||
}
|
||||
|
||||
// Validate and create session.
|
||||
if (!(url == _document->getUrl() && _document->createSession(sessionId)))
|
||||
{
|
||||
LOG_DBG("CreateSession failed.");
|
||||
}
|
||||
}
|
||||
|
||||
else if (tokens.equals(0, "exit"))
|
||||
{
|
||||
#if !MOBILEAPP
|
||||
LOG_INF("Terminating immediately due to parent 'exit' command.");
|
||||
flushTraceEventRecordings();
|
||||
_document.reset();
|
||||
if (!Util::isKitInProcess())
|
||||
Util::forcedExit(EX_OK);
|
||||
else
|
||||
SigUtil::setTerminationFlag();
|
||||
#else
|
||||
#ifdef IOS
|
||||
LOG_INF("Setting our KitSocketPoll's termination flag due to 'exit' command.");
|
||||
std::unique_lock<std::mutex> lock(_ksPoll->terminationMutex);
|
||||
_ksPoll->terminationFlag = true;
|
||||
_ksPoll->terminationCV.notify_all();
|
||||
#else
|
||||
LOG_INF("Setting TerminationFlag due to 'exit' command.");
|
||||
SigUtil::setTerminationFlag();
|
||||
#endif
|
||||
_document.reset();
|
||||
#endif
|
||||
}
|
||||
else if (tokens.equals(0, "tile") || tokens.equals(0, "tilecombine") ||
|
||||
tokens.equals(0, "paintwindow") || tokens.equals(0, "resizewindow") ||
|
||||
COOLProtocol::getFirstToken(tokens[0], '-') == "child")
|
||||
{
|
||||
if (_document)
|
||||
{
|
||||
_queue->put(message);
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG_WRN("No document while processing " << tokens[0] << " request.");
|
||||
}
|
||||
}
|
||||
else if (tokens.size() == 3 && tokens.equals(0, "setconfig"))
|
||||
{
|
||||
#if !MOBILEAPP && !defined(BUILDING_TESTS)
|
||||
// Currently only rlimit entries are supported.
|
||||
if (!Rlimit::handleSetrlimitCommand(tokens))
|
||||
{
|
||||
LOG_ERR("Unknown setconfig command: " << message);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
else if (tokens.equals(0, "setloglevel"))
|
||||
{
|
||||
Log::logger().setLevel(tokens[1]);
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG_ERR("Bad or unknown token [" << tokens[0] << ']');
|
||||
}
|
||||
}
|
||||
|
||||
void KitWebSocketHandler::enableProcessInput(bool enable)
|
||||
{
|
||||
WebSocketHandler::enableProcessInput(enable);
|
||||
if (_document)
|
||||
_document->enableProcessInput(enable);
|
||||
|
||||
// Wake up poll to process data from socket input buffer
|
||||
if (enable && _ksPoll)
|
||||
_ksPoll->wakeup();
|
||||
}
|
||||
|
||||
void KitWebSocketHandler::onDisconnect()
|
||||
{
|
||||
#if !MOBILEAPP
|
||||
//FIXME: We could try to recover.
|
||||
LOG_ERR("Kit for DocBroker ["
|
||||
<< _docKey
|
||||
<< "] connection lost without exit arriving from wsd. Setting TerminationFlag");
|
||||
SigUtil::setTerminationFlag();
|
||||
#endif
|
||||
#ifdef IOS
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(_ksPoll->terminationMutex);
|
||||
_ksPoll->terminationFlag = true;
|
||||
_ksPoll->terminationCV.notify_all();
|
||||
}
|
||||
#endif
|
||||
_ksPoll.reset();
|
||||
}
|
||||
|
||||
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
|
|
@ -0,0 +1,56 @@
|
|||
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
|
||||
/*
|
||||
* Copyright the Collabora Online contributors.
|
||||
*
|
||||
* SPDX-License-Identifier: MPL-2.0
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
/*
|
||||
* The main entry point for the LibreOfficeKit process serving
|
||||
* a document editing session.
|
||||
*/
|
||||
|
||||
#include <net/WebSocketHandler.hpp>
|
||||
|
||||
class Document;
|
||||
class TileQueue;
|
||||
class KitSocketPoll;
|
||||
|
||||
class KitWebSocketHandler final : public WebSocketHandler
|
||||
{
|
||||
std::shared_ptr<TileQueue> _queue;
|
||||
std::string _socketName;
|
||||
std::shared_ptr<lok::Office> _loKit;
|
||||
std::string _jailId;
|
||||
std::string _docKey; //< When we get it while creating a new view.
|
||||
std::shared_ptr<Document> _document;
|
||||
std::shared_ptr<KitSocketPoll> _ksPoll;
|
||||
const unsigned _mobileAppDocId;
|
||||
|
||||
public:
|
||||
KitWebSocketHandler(const std::string& socketName, const std::shared_ptr<lok::Office>& loKit,
|
||||
const std::string& jailId, std::shared_ptr<KitSocketPoll> ksPoll,
|
||||
unsigned mobileAppDocId)
|
||||
: WebSocketHandler(/* isClient = */ true, /* isMasking */ false)
|
||||
, _queue(std::make_shared<TileQueue>())
|
||||
, _socketName(socketName)
|
||||
, _loKit(loKit)
|
||||
, _jailId(jailId)
|
||||
, _ksPoll(std::move(ksPoll))
|
||||
, _mobileAppDocId(mobileAppDocId)
|
||||
{
|
||||
}
|
||||
|
||||
~KitWebSocketHandler()
|
||||
{
|
||||
// Just to make it easier to set a breakpoint
|
||||
}
|
||||
|
||||
protected:
|
||||
virtual void handleMessage(const std::vector<char>& data) override;
|
||||
virtual void enableProcessInput(bool enable = true) override;
|
||||
virtual void onDisconnect() override;
|
||||
};
|
|
@ -0,0 +1,117 @@
|
|||
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
|
||||
/*
|
||||
* Copyright the Collabora Online contributors.
|
||||
*
|
||||
* SPDX-License-Identifier: MPL-2.0
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
/*
|
||||
* When the session is inactive, we need to record its state for a replay.
|
||||
*/
|
||||
|
||||
struct RecordedEvent
|
||||
{
|
||||
private:
|
||||
int _type = 0;
|
||||
std::string _payload;
|
||||
|
||||
public:
|
||||
RecordedEvent() {}
|
||||
|
||||
RecordedEvent(int type, const std::string& payload)
|
||||
: _type(type)
|
||||
, _payload(payload)
|
||||
{
|
||||
}
|
||||
|
||||
void setType(int type) { _type = type; }
|
||||
|
||||
int getType() const { return _type; }
|
||||
|
||||
void setPayload(const std::string& payload) { _payload = payload; }
|
||||
|
||||
const std::string& getPayload() const { return _payload; }
|
||||
};
|
||||
|
||||
class StateRecorder
|
||||
{
|
||||
private:
|
||||
bool _invalidate;
|
||||
std::unordered_map<std::string, std::string> _recordedStates;
|
||||
std::unordered_map<int, std::unordered_map<int, RecordedEvent>> _recordedViewEvents;
|
||||
std::unordered_map<int, RecordedEvent> _recordedEvents;
|
||||
std::vector<RecordedEvent> _recordedEventsVector;
|
||||
|
||||
public:
|
||||
StateRecorder()
|
||||
: _invalidate(false)
|
||||
{
|
||||
}
|
||||
|
||||
// TODO Remember the maximal area we need to invalidate - grow it step by step.
|
||||
void recordInvalidate() { _invalidate = true; }
|
||||
|
||||
bool isInvalidate() const { return _invalidate; }
|
||||
|
||||
const std::unordered_map<std::string, std::string>& getRecordedStates() const
|
||||
{
|
||||
return _recordedStates;
|
||||
}
|
||||
|
||||
const std::unordered_map<int, std::unordered_map<int, RecordedEvent>>&
|
||||
getRecordedViewEvents() const
|
||||
{
|
||||
return _recordedViewEvents;
|
||||
}
|
||||
|
||||
const std::unordered_map<int, RecordedEvent>& getRecordedEvents() const
|
||||
{
|
||||
return _recordedEvents;
|
||||
}
|
||||
|
||||
const std::vector<RecordedEvent>& getRecordedEventsVector() const
|
||||
{
|
||||
return _recordedEventsVector;
|
||||
}
|
||||
|
||||
void recordEvent(const int type, const std::string& payload)
|
||||
{
|
||||
_recordedEvents[type] = RecordedEvent(type, payload);
|
||||
}
|
||||
|
||||
void recordViewEvent(const int viewId, const int type, const std::string& payload)
|
||||
{
|
||||
_recordedViewEvents[viewId][type] = { type, payload };
|
||||
}
|
||||
|
||||
void recordState(const std::string& name, const std::string& value)
|
||||
{
|
||||
_recordedStates[name] = value;
|
||||
}
|
||||
|
||||
/// In the case we need to remember all the events that come, not just
|
||||
/// the final state.
|
||||
void recordEventSequence(const int type, const std::string& payload)
|
||||
{
|
||||
_recordedEventsVector.emplace_back(type, payload);
|
||||
}
|
||||
|
||||
void dumpState(std::ostream&)
|
||||
{
|
||||
// TODO: the rest ...
|
||||
}
|
||||
|
||||
void clear()
|
||||
{
|
||||
_invalidate = false;
|
||||
_recordedEvents.clear();
|
||||
_recordedViewEvents.clear();
|
||||
_recordedStates.clear();
|
||||
_recordedEventsVector.clear();
|
||||
}
|
||||
};
|
||||
|
||||
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
|
|
@ -113,9 +113,10 @@ endif
|
|||
AM_CPPFLAGS = -pthread -I$(top_srcdir) -DBUILDING_TESTS -DLOK_ABORT_ON_ASSERTION
|
||||
|
||||
wsd_sources = \
|
||||
../common/SpookyV2.cpp \
|
||||
../common/Authorization.cpp \
|
||||
../common/SpookyV2.cpp \
|
||||
../kit/Kit.cpp \
|
||||
../kit/KitWebSocket.cpp \
|
||||
../kit/TestStubs.cpp \
|
||||
../wsd/FileServerUtil.cpp \
|
||||
../wsd/RequestDetails.cpp \
|
||||
|
|
|
@ -38,6 +38,7 @@ online_SOURCES = \
|
|||
../common/Util.cpp \
|
||||
../kit/ChildSession.cpp \
|
||||
../kit/Kit.cpp \
|
||||
../kit/KitWebSocket.cpp \
|
||||
../kit/DeltaSimd.c \
|
||||
../net/FakeSocket.cpp \
|
||||
../net/Socket.cpp \
|
||||
|
|
Loading…
Reference in New Issue