libreoffice-online/kit/ChildSession.hpp
Tor Lillqvist 95eb849217 Still more iOS app and related Online C++ code hacking
Re-think the plumbing between the different parts of the C++ Online
code. Do try to have it work more like in real Online on all but the
lowest socket level. Except that we don't have multiple processes, but
threads inside the same process. And instead of using actual system
sockets for WebSocket traffic between the threads, we use our own
FakeSocket things, with no WebSocket framing of messages.

Reduce the amount of #ifdef MOBILEAPP a bit also by compiling in the
UnitFoo things. Hardcode that so that no unit testing is ever
attempted, though. We don't try to dlopen any library.

Corresponding changes in the app Objective-C code. Plus fixes and
functionality improvements.

Now it gets so far that the JavaScript code thinks it has the document
tiles presented, and doesn't crash. But it hangs occasionally. And all
tiles show up blank.

Anyway, progress.

Change-Id: I769497c9a46ddb74984bc7af36d132b7b43895d4
2018-09-19 11:31:18 +03:00

237 lines
8.7 KiB
C++

/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
/*
* This file is part of the LibreOffice project.
*
* 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/.
*/
#ifndef INCLUDED_CHILDSESSION_HPP
#define INCLUDED_CHILDSESSION_HPP
#include <mutex>
#include <unordered_map>
#include <queue>
#define LOK_USE_UNSTABLE_API
#include <LibreOfficeKit/LibreOfficeKit.hxx>
#include <Poco/Net/WebSocket.h>
#include <Poco/Thread.h>
#include "Common.hpp"
#include "Kit.hpp"
#include "Session.hpp"
class ChildSession;
enum class LokEventTargetEnum
{
Document,
Window
};
// An abstract interface.
class DocumentManagerInterface
{
public:
/// Reqest loading a document, or a new view, if one exists.
virtual bool onLoad(const std::string& sessionId,
const std::string& jailedFilePath,
const std::string& userName,
const std::string& docPassword,
const std::string& renderOpts,
const bool haveDocPassword,
const std::string& lang,
const std::string& watermarkText) = 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 document instance.
virtual std::shared_ptr<lok::Document> getLOKitDocument() = 0;
/// Send updated view info to all active sessions.
virtual void notifyViewInfo() = 0;
virtual void updateEditorSpeeds(int id, int speed) = 0;
virtual int getEditorId() = 0;
/// Get a view ID <-> UserInfo map.
virtual std::map<int, UserInfo> getViewInfo() = 0;
virtual std::mutex& getMutex() = 0;
/// Mutex guarding the document - so that we can lock operations like
/// setting a view followed by a tile render, etc.
virtual std::mutex& getDocumentMutex() = 0;
virtual std::shared_ptr<TileQueue>& getTileQueue() = 0;
virtual bool sendFrame(const char* buffer, int length, WSOpCode opCode = WSOpCode::Text) = 0;
};
struct RecordedEvent
{
int _type;
std::string _payload;
};
/// When the session is inactive, we need to record its state for a replay.
class StateRecorder
{
public:
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;
StateRecorder() : _invalidate(false) {}
// TODO Remember the maximal area we need to invalidate - grow it step by step.
void recordInvalidate()
{
_invalidate = true;
}
void recordEvent(const int type, const std::string& payload)
{
_recordedEvents[type] = {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 rememeber all the events that come, not just
/// the final state.
void recordEventSequence(const int type, const std::string& payload)
{
_recordedEventsVector.push_back({type, payload});
}
void clear()
{
_invalidate = false;
_recordedEvents.clear();
_recordedViewEvents.clear();
_recordedStates.clear();
_recordedEventsVector.clear();
}
};
/// Represents a session to the WSD process, in a Kit process. Note that this is not a singleton.
class ChildSession final : public Session
{
public:
/// Create a new ChildSession
/// ws The socket between master and kit (jailed).
/// loKit The LOKit instance.
/// loKitDocument The instance to an existing document (when opening
/// a new view) or nullptr (when first view).
/// jailId The JailID of the jail root directory,
// used by downloadas to construct jailed path.
ChildSession(const std::string& id,
const std::string& jailId,
DocumentManagerInterface& docManager);
virtual ~ChildSession();
bool getStatus(const char* buffer, int length);
int getViewId() const { return _viewId; }
void setViewId(const int viewId) { _viewId = viewId; }
const std::string& getViewUserId() const { return _userId; }
const std::string& getViewUserName() const { return _userName; }
const std::string& getViewUserExtraInfo() const { return _userExtraInfo; }
const std::string& getWatermarkText() const { return _watermarkText; }
void updateSpeed();
int getSpeed();
void loKitCallback(const int type, const std::string& payload);
bool sendTextFrame(const char* buffer, int length) override
{
const auto msg = "client-" + getId() + ' ' + std::string(buffer, length);
const std::unique_lock<std::mutex> lock = getLock();
return _docManager.sendFrame(msg.data(), msg.size(), WSOpCode::Text);
}
bool sendBinaryFrame(const char* buffer, int length) override
{
const auto msg = "client-" + getId() + ' ' + std::string(buffer, length);
const std::unique_lock<std::mutex> lock = getLock();
return _docManager.sendFrame(msg.data(), msg.size(), WSOpCode::Binary);
}
using Session::sendTextFrame;
private:
bool loadDocument(const char* buffer, int length, const std::vector<std::string>& tokens);
bool sendFontRendering(const char* buffer, int length, const std::vector<std::string>& tokens);
bool getCommandValues(const char* buffer, int length, const std::vector<std::string>& tokens);
bool clientZoom(const char* buffer, int length, const std::vector<std::string>& tokens);
bool clientVisibleArea(const char* buffer, int length, const std::vector<std::string>& tokens);
bool outlineState(const char* buffer, int length, const std::vector<std::string>& tokens);
bool downloadAs(const char* buffer, int length, const std::vector<std::string>& tokens);
bool getChildId();
bool getTextSelection(const char* buffer, int length, const std::vector<std::string>& tokens);
bool paste(const char* buffer, int length, const std::vector<std::string>& tokens);
bool insertFile(const char* buffer, int length, const std::vector<std::string>& tokens);
bool keyEvent(const char* buffer, int length, const std::vector<std::string>& tokens, const LokEventTargetEnum target);
bool extTextInputEvent(const char* /*buffer*/, int /*length*/, const std::vector<std::string>& tokens);
bool dialogKeyEvent(const char* buffer, int length, const std::vector<std::string>& tokens);
bool mouseEvent(const char* buffer, int length, const std::vector<std::string>& tokens, const LokEventTargetEnum target);
bool unoCommand(const char* buffer, int length, const std::vector<std::string>& tokens);
bool selectText(const char* buffer, int length, const std::vector<std::string>& tokens);
bool selectGraphic(const char* buffer, int length, const std::vector<std::string>& tokens);
bool renderWindow(const char* buffer, int length, const std::vector<std::string>& tokens);
bool resetSelection(const char* buffer, int length, const std::vector<std::string>& tokens);
bool saveAs(const char* buffer, int length, const std::vector<std::string>& tokens);
bool setClientPart(const char* buffer, int length, const std::vector<std::string>& tokens);
bool setPage(const char* buffer, int length, const std::vector<std::string>& tokens);
bool sendWindowCommand(const char* buffer, int length, const std::vector<std::string>& tokens);
void rememberEventsForInactiveUser(const int type, const std::string& payload);
virtual void disconnect() override;
virtual bool _handleInput(const char* buffer, int length) override;
std::shared_ptr<lok::Document> getLOKitDocument()
{
return _docManager.getLOKitDocument();
}
private:
const std::string _jailId;
DocumentManagerInterface& _docManager;
std::queue<std::chrono::steady_clock::time_point> _cursorInvalidatedEvent;
const unsigned _eventStorageIntervalMs = 15*1000;
/// View ID, returned by createView() or 0 by default.
int _viewId;
/// Whether document has been opened succesfuly
bool _isDocLoaded;
std::string _docType;
StateRecorder _stateRecorder;
/// Synchronize _loKitDocument access.
/// This should be owned by Document.
static std::recursive_mutex Mutex;
};
#endif
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */