bff5748cd9
All changes are supposed to be persistent. This simplifies the tile caching code quite a lot. The TileCache object no longer needs to keep any state whether the document is being edited or whether it has been modified without saving etc. Update the modtime.txt file after saving the document. Otherwise the tile cache would wrongly be considered invalid next time. As a sanity check, we put a flag file 'unsaved.txt' into the cache directory whenever we get a callback that indicates the document has been modified, and remove it when the document is saved. If the flag file is present when we take an existing tile cache into use, we can't trust it. Even after these changes, we still don't use an existing tile cache as much (or at all?) as we could, though. The INVALIDATE_TILES EMPTY callback that LO does early on in a conection causes us to remove all cached tiles...
208 lines
6 KiB
C++
208 lines
6 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_DOCUMENTBROKER_HPP
|
|
#define INCLUDED_DOCUMENTBROKER_HPP
|
|
|
|
#include <signal.h>
|
|
|
|
#include <atomic>
|
|
#include <chrono>
|
|
#include <memory>
|
|
#include <mutex>
|
|
#include <string>
|
|
#include <map>
|
|
|
|
#include <Poco/URI.h>
|
|
|
|
#include "IoUtil.hpp"
|
|
#include "MasterProcessSession.hpp"
|
|
#include "Util.hpp"
|
|
|
|
// Forwards.
|
|
class StorageBase;
|
|
class TileCache;
|
|
|
|
/// Represents a new LOK child that is read
|
|
/// to host a document.
|
|
class ChildProcess
|
|
{
|
|
public:
|
|
ChildProcess() :
|
|
_pid(-1)
|
|
{
|
|
}
|
|
|
|
/// pid is the process ID of the child.
|
|
/// ws is the control WebSocket to the child.
|
|
ChildProcess(const Poco::Process::PID pid, const std::shared_ptr<Poco::Net::WebSocket>& ws) :
|
|
_pid(pid),
|
|
_ws(ws)
|
|
{
|
|
Log::info("ChildProcess ctor [" + std::to_string(_pid) + "].");
|
|
}
|
|
|
|
ChildProcess(ChildProcess&& other) :
|
|
_pid(other._pid),
|
|
_ws(other._ws)
|
|
{
|
|
Log::info("ChildProcess move ctor [" + std::to_string(_pid) + "].");
|
|
other._pid = -1;
|
|
other._ws.reset();
|
|
}
|
|
|
|
const ChildProcess& operator=(ChildProcess&& other)
|
|
{
|
|
Log::info("ChildProcess assign [" + std::to_string(_pid) + "].");
|
|
_pid = other._pid;
|
|
other._pid = -1;
|
|
_ws = other._ws;
|
|
other._ws.reset();
|
|
|
|
return *this;
|
|
}
|
|
|
|
~ChildProcess()
|
|
{
|
|
if (_pid > 0)
|
|
{
|
|
Log::info("~ChildProcess dtor [" + std::to_string(_pid) + "].");
|
|
close(false);
|
|
}
|
|
}
|
|
|
|
void close(const bool rude)
|
|
{
|
|
_ws.reset();
|
|
if (_pid != -1)
|
|
{
|
|
Log::info("Closing child [" + std::to_string(_pid) + "].");
|
|
if (rude && kill(_pid, SIGINT) != 0 && kill(_pid, 0) != 0)
|
|
{
|
|
Log::syserror("Cannot terminate lokit [" + std::to_string(_pid) + "]. Abandoning.");
|
|
}
|
|
|
|
_pid = -1;
|
|
}
|
|
}
|
|
|
|
Poco::Process::PID getPid() const { return _pid; }
|
|
std::shared_ptr<Poco::Net::WebSocket> getWebSocket() const { return _ws; }
|
|
|
|
private:
|
|
Poco::Process::PID _pid;
|
|
std::shared_ptr<Poco::Net::WebSocket> _ws;
|
|
};
|
|
|
|
/// DocumentBroker is responsible for setting up a document
|
|
/// in jail and brokering loading it from Storage
|
|
/// and saving it back.
|
|
/// Contains URI, physical path, etc.
|
|
class DocumentBroker
|
|
{
|
|
public:
|
|
|
|
static Poco::URI sanitizeURI(const std::string& uri);
|
|
|
|
/// Returns a document-specific key based
|
|
/// on the URI of the document.
|
|
static
|
|
std::string getDocKey(const Poco::URI& uri);
|
|
|
|
DocumentBroker(const Poco::URI& uriPublic,
|
|
const std::string& docKey,
|
|
const std::string& childRoot,
|
|
std::shared_ptr<ChildProcess> childProcess);
|
|
|
|
~DocumentBroker()
|
|
{
|
|
Log::info() << "~DocumentBroker [" << _uriPublic.toString()
|
|
<< "] destroyed with " << getSessionsCount()
|
|
<< " sessions left." << Log::end;
|
|
}
|
|
|
|
void validate(const Poco::URI& uri);
|
|
|
|
/// Loads a document from the public URI into the jail.
|
|
bool load(const std::string& jailId);
|
|
|
|
bool save();
|
|
|
|
/// Save the document if there was activity since last save.
|
|
/// force when true, will force saving immediatly, regardless
|
|
/// of how long ago the activity was.
|
|
bool autoSave(const bool force);
|
|
|
|
/// Wait until the document is saved next.
|
|
/// This is used to cleanup after the last save.
|
|
/// Returns false if times out.
|
|
bool waitSave(const size_t timeoutMs);
|
|
|
|
Poco::URI getPublicUri() const { return _uriPublic; }
|
|
Poco::URI getJailedUri() const { return _uriJailed; }
|
|
const std::string& getJailId() const { return _jailId; }
|
|
const std::string& getDocKey() const { return _docKey; }
|
|
const std::string& getFilename() const { return _filename; };
|
|
TileCache& tileCache() { return *_tileCache; }
|
|
size_t getSessionsCount() const
|
|
{
|
|
std::lock_guard<std::mutex> lock(_mutex);
|
|
return _sessions.size();
|
|
}
|
|
|
|
/// Returns the time in milliseconds since last save.
|
|
double getTimeSinceLastSaveMs() const
|
|
{
|
|
const auto duration = (std::chrono::steady_clock::now() - _lastSaveTime);
|
|
return std::chrono::duration_cast<std::chrono::milliseconds>(duration).count();
|
|
}
|
|
|
|
std::string getJailRoot() const;
|
|
|
|
/// Ignore input events from all web socket sessions
|
|
/// except this one
|
|
void takeEditLock(const std::string& id);
|
|
|
|
/// Add a new session. Returns the new number of sessions.
|
|
size_t addSession(std::shared_ptr<MasterProcessSession>& session);
|
|
/// Removes a session by ID. Returns the new number of sessions.
|
|
size_t removeSession(const std::string& id);
|
|
|
|
// Called when the last view is going out.
|
|
bool canDestroy();
|
|
bool isMarkedToDestroy() const { return _markToDestroy; }
|
|
bool isModified() const { return _isModified; }
|
|
void setModified(const bool value);
|
|
|
|
private:
|
|
const Poco::URI _uriPublic;
|
|
const std::string _docKey;
|
|
const std::string _childRoot;
|
|
const std::string _cacheRoot;
|
|
Poco::URI _uriJailed;
|
|
std::string _jailId;
|
|
std::string _filename;
|
|
std::chrono::steady_clock::time_point _lastSaveTime;
|
|
std::map<std::string, std::shared_ptr<MasterProcessSession>> _sessions;
|
|
std::unique_ptr<StorageBase> _storage;
|
|
std::unique_ptr<TileCache> _tileCache;
|
|
std::shared_ptr<ChildProcess> _childProcess;
|
|
bool _markToDestroy;
|
|
bool _isModified;
|
|
mutable std::mutex _mutex;
|
|
std::condition_variable _saveCV;
|
|
std::mutex _saveMutex;
|
|
|
|
static constexpr auto IdleSaveDurationMs = 30 * 1000;
|
|
static constexpr auto AutoSaveDurationMs = 300 * 1000;
|
|
};
|
|
|
|
#endif
|
|
|
|
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
|