2015-04-13 04:09:02 -05:00
|
|
|
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
|
2015-03-12 09:18:35 -05:00
|
|
|
/*
|
|
|
|
* 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/.
|
|
|
|
*/
|
|
|
|
|
2017-12-20 07:06:26 -06:00
|
|
|
#include <config.h>
|
2015-03-12 09:18:35 -05:00
|
|
|
|
2017-03-08 10:38:22 -06:00
|
|
|
#include "TileCache.hpp"
|
|
|
|
|
2015-05-28 04:53:14 -05:00
|
|
|
#include <cassert>
|
2015-05-29 00:39:21 -05:00
|
|
|
#include <climits>
|
2015-05-28 10:39:05 -05:00
|
|
|
#include <cstdio>
|
2015-03-12 09:18:35 -05:00
|
|
|
#include <fstream>
|
|
|
|
#include <iostream>
|
|
|
|
#include <memory>
|
2015-08-04 13:37:05 -05:00
|
|
|
#include <sstream>
|
2015-05-05 06:57:51 -05:00
|
|
|
#include <string>
|
2016-04-15 10:24:00 -05:00
|
|
|
#include <vector>
|
2015-03-12 09:18:35 -05:00
|
|
|
|
2015-05-29 00:49:49 -05:00
|
|
|
#include <Poco/DigestEngine.h>
|
2015-05-28 10:39:05 -05:00
|
|
|
#include <Poco/DirectoryIterator.h>
|
2015-05-29 04:23:57 -05:00
|
|
|
#include <Poco/Exception.h>
|
2015-03-12 09:18:35 -05:00
|
|
|
#include <Poco/File.h>
|
|
|
|
#include <Poco/Path.h>
|
2015-05-28 04:53:14 -05:00
|
|
|
#include <Poco/StringTokenizer.h>
|
2015-05-29 00:49:49 -05:00
|
|
|
#include <Poco/Timestamp.h>
|
2015-05-29 04:23:57 -05:00
|
|
|
#include <Poco/URI.h>
|
2015-03-12 09:18:35 -05:00
|
|
|
|
loolwsd: include cleanup and organization
A source file (.cpp) must include its own header first.
This insures that the header is self-contained and
doesn't depend on arbitrary (and accidental) includes
before it to compile.
Furthermore, system headers should go next, followed by
C then C++ headers, then libraries (Poco, etc) and, finally,
project headers come last.
This makes sure that headers and included in the same dependency
order to avoid side-effects. For example, Poco should never rely on
anything from our project in the same way that a C header should
never rely on anything in C++, Poco, or project headers.
Also, includes ought to be sorted where possible, to improve
readability and avoid accidental duplicates (of which there
were a few).
Change-Id: I62cc1343e4a091d69195e37ed659dba20cfcb1ef
Reviewed-on: https://gerrit.libreoffice.org/25262
Reviewed-by: Ashod Nakashian <ashnakash@gmail.com>
Tested-by: Ashod Nakashian <ashnakash@gmail.com>
2016-05-21 09:23:07 -05:00
|
|
|
#include "ClientSession.hpp"
|
2017-12-20 07:06:26 -06:00
|
|
|
#include <Common.hpp>
|
|
|
|
#include <Protocol.hpp>
|
2016-12-11 11:03:38 -06:00
|
|
|
#include "SenderQueue.hpp"
|
2017-12-20 07:06:26 -06:00
|
|
|
#include <Unit.hpp>
|
|
|
|
#include <Util.hpp>
|
|
|
|
#include <common/FileUtil.hpp>
|
2017-11-08 02:34:37 -06:00
|
|
|
|
|
|
|
using namespace LOOLProtocol;
|
2015-03-12 09:18:35 -05:00
|
|
|
|
2015-05-29 00:49:49 -05:00
|
|
|
using Poco::DirectoryIterator;
|
|
|
|
using Poco::File;
|
|
|
|
using Poco::StringTokenizer;
|
|
|
|
using Poco::Timestamp;
|
|
|
|
|
2016-03-26 06:50:13 -05:00
|
|
|
TileCache::TileCache(const std::string& docURL,
|
2016-04-01 10:56:08 -05:00
|
|
|
const Timestamp& modifiedTime,
|
2016-04-22 06:00:11 -05:00
|
|
|
const std::string& cacheDir) :
|
2015-06-24 15:05:49 -05:00
|
|
|
_docURL(docURL),
|
2016-12-12 05:05:05 -06:00
|
|
|
_cacheDir(cacheDir)
|
2015-03-12 18:34:42 -05:00
|
|
|
{
|
2017-03-11 13:42:50 -06:00
|
|
|
LOG_INF("TileCache ctor for uri [" << _docURL <<
|
2017-04-04 23:26:09 -05:00
|
|
|
"], cacheDir: [" << _cacheDir <<
|
|
|
|
"], modifiedTime=" << (modifiedTime.raw()/1000000) <<
|
2017-03-11 13:42:50 -06:00
|
|
|
" getLastModified()=" << (getLastModified().raw()/1000000));
|
2016-04-22 06:00:11 -05:00
|
|
|
File directory(_cacheDir);
|
2017-01-17 09:42:31 -06:00
|
|
|
std::string unsaved;
|
2016-04-22 06:00:11 -05:00
|
|
|
if (directory.exists() &&
|
|
|
|
(getLastModified() < modifiedTime ||
|
2017-01-17 09:42:31 -06:00
|
|
|
getTextFile("unsaved.txt", unsaved)))
|
2016-03-26 09:42:15 -05:00
|
|
|
{
|
2016-04-22 06:00:11 -05:00
|
|
|
// Document changed externally or modifications were not saved after all. Cache not useful.
|
2017-04-12 13:04:30 -05:00
|
|
|
completeCleanup();
|
2016-03-26 09:42:15 -05:00
|
|
|
}
|
|
|
|
|
2016-04-22 06:00:11 -05:00
|
|
|
File(_cacheDir).createDirectories();
|
2016-03-26 09:42:15 -05:00
|
|
|
|
|
|
|
saveLastModified(modifiedTime);
|
2015-03-12 09:18:35 -05:00
|
|
|
}
|
|
|
|
|
2016-03-26 06:50:13 -05:00
|
|
|
TileCache::~TileCache()
|
|
|
|
{
|
2017-06-22 07:58:50 -05:00
|
|
|
_owner = std::thread::id(0);
|
2017-03-11 13:42:50 -06:00
|
|
|
LOG_INF("~TileCache dtor for uri [" << _docURL << "].");
|
2016-04-15 10:24:00 -05:00
|
|
|
}
|
|
|
|
|
2017-04-12 13:04:30 -05:00
|
|
|
void TileCache::completeCleanup() const
|
|
|
|
{
|
|
|
|
FileUtil::removeFile(_cacheDir, true);
|
|
|
|
LOG_INF("Completely cleared tile cache: " << _cacheDir);
|
|
|
|
}
|
|
|
|
|
2016-08-13 23:01:13 -05:00
|
|
|
/// Tracks the rendering of a given tile
|
|
|
|
/// to avoid duplication and help clock
|
|
|
|
/// rendering latency.
|
2016-04-25 04:04:25 -05:00
|
|
|
struct TileCache::TileBeingRendered
|
2016-04-15 10:24:00 -05:00
|
|
|
{
|
2016-05-16 19:49:36 -05:00
|
|
|
std::vector<std::weak_ptr<ClientSession>> _subscribers;
|
2016-09-21 17:47:00 -05:00
|
|
|
|
|
|
|
TileBeingRendered(const std::string& cachedName, const TileDesc& tile)
|
2016-05-22 13:31:18 -05:00
|
|
|
: _startTime(std::chrono::steady_clock::now()),
|
2016-09-21 17:47:00 -05:00
|
|
|
_tile(tile),
|
|
|
|
_cachedName(cachedName)
|
2016-04-25 04:04:25 -05:00
|
|
|
{
|
|
|
|
}
|
2016-05-15 09:19:49 -05:00
|
|
|
|
2016-09-21 17:47:00 -05:00
|
|
|
const TileDesc& getTile() const { return _tile; }
|
2016-05-22 14:20:24 -05:00
|
|
|
const std::string& getCacheName() const { return _cachedName; }
|
2016-09-21 17:47:00 -05:00
|
|
|
int getVersion() const { return _tile.getVersion(); }
|
|
|
|
void setVersion(int version) { _tile.setVersion(version); }
|
2016-05-22 13:31:18 -05:00
|
|
|
|
2016-05-15 09:19:49 -05:00
|
|
|
std::chrono::steady_clock::time_point getStartTime() const { return _startTime; }
|
2016-05-22 18:17:01 -05:00
|
|
|
double getElapsedTimeMs() const { return std::chrono::duration_cast<std::chrono::milliseconds>
|
|
|
|
(std::chrono::steady_clock::now() - _startTime).count(); }
|
2016-05-15 09:19:49 -05:00
|
|
|
private:
|
|
|
|
std::chrono::steady_clock::time_point _startTime;
|
2016-09-21 17:47:00 -05:00
|
|
|
TileDesc _tile;
|
2016-05-22 14:20:24 -05:00
|
|
|
std::string _cachedName;
|
2016-04-25 04:04:25 -05:00
|
|
|
};
|
2016-04-15 10:24:00 -05:00
|
|
|
|
2016-05-15 17:47:08 -05:00
|
|
|
std::shared_ptr<TileCache::TileBeingRendered> TileCache::findTileBeingRendered(const TileDesc& tileDesc)
|
2016-04-15 10:24:00 -05:00
|
|
|
{
|
2016-05-15 17:47:08 -05:00
|
|
|
const std::string cachedName = cacheFileName(tileDesc);
|
2016-04-15 10:24:00 -05:00
|
|
|
|
2017-06-22 07:58:50 -05:00
|
|
|
assertCorrectThread();
|
2016-04-15 10:24:00 -05:00
|
|
|
|
2016-05-01 19:34:39 -05:00
|
|
|
const auto tile = _tilesBeingRendered.find(cachedName);
|
2017-03-30 04:15:28 -05:00
|
|
|
return tile != _tilesBeingRendered.end() ? tile->second : nullptr;
|
2016-04-15 10:24:00 -05:00
|
|
|
}
|
|
|
|
|
2018-07-23 09:09:55 -05:00
|
|
|
void TileCache::forgetTileBeingRendered(std::shared_ptr<TileCache::TileBeingRendered> tileBeingRendered, const TileDesc& tile)
|
2016-04-15 10:24:00 -05:00
|
|
|
{
|
2017-06-22 07:58:50 -05:00
|
|
|
assertCorrectThread();
|
2018-07-23 09:09:55 -05:00
|
|
|
assert(tileBeingRendered);
|
|
|
|
assert(_tilesBeingRendered.find(tileBeingRendered->getCacheName()) != _tilesBeingRendered.end());
|
2016-04-15 10:24:00 -05:00
|
|
|
|
2018-07-23 09:09:55 -05:00
|
|
|
for(auto& subscriber : tileBeingRendered->_subscribers)
|
|
|
|
{
|
|
|
|
std::shared_ptr<ClientSession> session = subscriber.lock();
|
|
|
|
if(session && tile.getId() == -1)
|
|
|
|
session->traceUnSubscribe();
|
|
|
|
}
|
|
|
|
|
|
|
|
_tilesBeingRendered.erase(tileBeingRendered->getCacheName());
|
2016-03-26 06:50:13 -05:00
|
|
|
}
|
|
|
|
|
2018-06-19 09:41:02 -05:00
|
|
|
bool TileCache::hasTileBeingRendered(const TileDesc& tile)
|
|
|
|
{
|
|
|
|
return findTileBeingRendered(tile) != nullptr;
|
|
|
|
}
|
|
|
|
|
2016-05-15 17:47:08 -05:00
|
|
|
std::unique_ptr<std::fstream> TileCache::lookupTile(const TileDesc& tile)
|
2015-03-12 09:18:35 -05:00
|
|
|
{
|
2016-05-15 17:47:08 -05:00
|
|
|
const std::string fileName = _cacheDir + "/" + cacheFileName(tile);
|
2015-03-12 09:18:35 -05:00
|
|
|
|
2016-04-14 19:27:19 -05:00
|
|
|
std::unique_ptr<std::fstream> result(new std::fstream(fileName, std::ios::in));
|
2016-05-15 17:47:08 -05:00
|
|
|
UnitWSD::get().lookupTile(tile.getPart(), tile.getWidth(), tile.getHeight(),
|
|
|
|
tile.getTilePosX(), tile.getTilePosY(),
|
|
|
|
tile.getTileWidth(), tile.getTileHeight(), result);
|
2016-05-01 19:34:39 -05:00
|
|
|
|
2016-04-14 19:27:19 -05:00
|
|
|
if (result && result->is_open())
|
2016-03-26 07:20:48 -05:00
|
|
|
{
|
2017-03-11 13:42:50 -06:00
|
|
|
LOG_TRC("Found cache tile: " << fileName);
|
2016-04-14 19:27:19 -05:00
|
|
|
return result;
|
2016-03-26 07:20:48 -05:00
|
|
|
}
|
2015-06-24 15:05:49 -05:00
|
|
|
|
2016-04-14 19:27:19 -05:00
|
|
|
return nullptr;
|
2015-03-12 09:18:35 -05:00
|
|
|
}
|
|
|
|
|
2016-09-25 10:33:37 -05:00
|
|
|
void TileCache::saveTileAndNotify(const TileDesc& tile, const char *data, const size_t size)
|
2015-03-12 09:18:35 -05:00
|
|
|
{
|
2017-06-22 07:58:50 -05:00
|
|
|
assertCorrectThread();
|
2015-06-24 15:05:49 -05:00
|
|
|
|
2016-05-22 14:20:24 -05:00
|
|
|
std::shared_ptr<TileBeingRendered> tileBeingRendered = findTileBeingRendered(tile);
|
2015-03-13 07:17:51 -05:00
|
|
|
|
2018-07-23 09:09:55 -05:00
|
|
|
// Kit did not send image data, because tile has the same wireID as the previously sent tile
|
|
|
|
// We need to remove only the subscriptions
|
|
|
|
if(size == 0)
|
|
|
|
{
|
|
|
|
if(tileBeingRendered && tileBeingRendered->getVersion() <= tile.getVersion())
|
|
|
|
{
|
|
|
|
forgetTileBeingRendered(tileBeingRendered, tile);
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2016-05-22 14:20:24 -05:00
|
|
|
// Save to disk.
|
2018-02-07 03:17:59 -06:00
|
|
|
const std::string cachedName = (tileBeingRendered ? tileBeingRendered->getCacheName()
|
2016-05-22 14:20:24 -05:00
|
|
|
: cacheFileName(tile));
|
2016-09-27 07:48:32 -05:00
|
|
|
|
2016-12-08 21:48:25 -06:00
|
|
|
// Ignore if we can't save the tile, things will work anyway, but slower.
|
|
|
|
// An error indication is supposed to be sent to all users in that case.
|
2016-09-25 10:33:37 -05:00
|
|
|
const auto fileName = _cacheDir + "/" + cachedName;
|
2016-11-12 15:38:13 -06:00
|
|
|
if (FileUtil::saveDataToFileSafely(fileName, data, size))
|
|
|
|
{
|
2017-03-11 13:42:50 -06:00
|
|
|
LOG_TRC("Saved cache tile: " << fileName);
|
2016-11-12 15:38:13 -06:00
|
|
|
}
|
2016-05-22 14:20:24 -05:00
|
|
|
|
|
|
|
// Notify subscribers, if any.
|
|
|
|
if (tileBeingRendered)
|
|
|
|
{
|
2018-02-07 03:17:59 -06:00
|
|
|
const size_t subscriberCount = tileBeingRendered->_subscribers.size();
|
2016-12-10 21:33:45 -06:00
|
|
|
if (subscriberCount > 0)
|
2016-05-22 14:20:24 -05:00
|
|
|
{
|
2016-08-31 22:34:41 -05:00
|
|
|
std::string response = tile.serialize("tile:");
|
2017-03-11 13:42:50 -06:00
|
|
|
LOG_DBG("Sending tile message to " << subscriberCount << " subscribers: " << response);
|
2016-08-31 22:34:41 -05:00
|
|
|
|
2016-12-17 13:28:11 -06:00
|
|
|
// Send to first subscriber as-is (without cache marker).
|
2017-01-21 18:51:02 -06:00
|
|
|
auto payload = std::make_shared<Message>(response,
|
2017-01-21 22:50:08 -06:00
|
|
|
Message::Dir::Out,
|
|
|
|
response.size() + 1 + size);
|
2016-12-17 13:28:11 -06:00
|
|
|
payload->append("\n", 1);
|
|
|
|
payload->append(data, size);
|
2016-10-24 18:57:37 -05:00
|
|
|
|
2016-12-08 22:08:50 -06:00
|
|
|
auto& firstSubscriber = tileBeingRendered->_subscribers[0];
|
2018-02-07 03:17:59 -06:00
|
|
|
std::shared_ptr<ClientSession> firstSession = firstSubscriber.lock();
|
2016-12-13 18:20:05 -06:00
|
|
|
if (firstSession)
|
2018-07-06 05:38:31 -05:00
|
|
|
{
|
2016-12-13 18:20:05 -06:00
|
|
|
firstSession->enqueueSendMessage(payload);
|
2018-07-06 05:38:31 -05:00
|
|
|
}
|
2016-05-22 14:20:24 -05:00
|
|
|
|
2016-12-10 21:33:45 -06:00
|
|
|
if (subscriberCount > 1)
|
2016-05-22 14:20:24 -05:00
|
|
|
{
|
2016-12-11 17:49:56 -06:00
|
|
|
// All others must get served from the cache.
|
|
|
|
response += " renderid=cached\n";
|
|
|
|
|
2016-12-10 21:33:45 -06:00
|
|
|
// Create a new Payload.
|
|
|
|
payload.reset();
|
2017-01-21 18:51:02 -06:00
|
|
|
payload = std::make_shared<Message>(response,
|
2017-01-21 22:50:08 -06:00
|
|
|
Message::Dir::Out,
|
|
|
|
response.size() + size);
|
2016-12-17 13:28:11 -06:00
|
|
|
payload->append(data, size);
|
2016-12-10 21:33:45 -06:00
|
|
|
|
|
|
|
for (size_t i = 1; i < subscriberCount; ++i)
|
|
|
|
{
|
|
|
|
auto& subscriber = tileBeingRendered->_subscribers[i];
|
2018-02-07 03:17:59 -06:00
|
|
|
std::shared_ptr<ClientSession> session = subscriber.lock();
|
2016-12-13 18:20:05 -06:00
|
|
|
if (session)
|
|
|
|
{
|
|
|
|
session->enqueueSendMessage(payload);
|
|
|
|
}
|
2016-12-10 21:33:45 -06:00
|
|
|
}
|
2016-05-22 14:20:24 -05:00
|
|
|
}
|
|
|
|
}
|
2016-09-19 19:21:18 -05:00
|
|
|
else
|
|
|
|
{
|
2017-03-11 13:42:50 -06:00
|
|
|
LOG_DBG("No subscribers for: " << cachedName);
|
2016-09-19 19:21:18 -05:00
|
|
|
}
|
2016-05-22 14:20:24 -05:00
|
|
|
|
|
|
|
// Remove subscriptions.
|
2016-09-20 21:26:19 -05:00
|
|
|
if (tileBeingRendered->getVersion() <= tile.getVersion())
|
2016-05-22 15:47:22 -05:00
|
|
|
{
|
2017-03-11 13:42:50 -06:00
|
|
|
LOG_DBG("STATISTICS: tile " << tile.getVersion() << " internal roundtrip " <<
|
|
|
|
tileBeingRendered->getElapsedTimeMs() << " ms.");
|
2018-07-23 09:09:55 -05:00
|
|
|
forgetTileBeingRendered(tileBeingRendered, tile);
|
2016-05-22 15:47:22 -05:00
|
|
|
}
|
2016-05-22 14:20:24 -05:00
|
|
|
}
|
2016-09-20 20:40:28 -05:00
|
|
|
else
|
|
|
|
{
|
2017-03-11 13:42:50 -06:00
|
|
|
LOG_DBG("No subscribers for: " << cachedName);
|
2016-09-20 20:40:28 -05:00
|
|
|
}
|
2015-03-12 18:34:42 -05:00
|
|
|
}
|
|
|
|
|
2017-01-17 09:42:31 -06:00
|
|
|
bool TileCache::getTextFile(const std::string& fileName, std::string& content)
|
2015-03-13 07:17:51 -05:00
|
|
|
{
|
2016-04-22 06:00:11 -05:00
|
|
|
const std::string fullFileName = _cacheDir + "/" + fileName;
|
2015-03-13 07:17:51 -05:00
|
|
|
|
2016-04-22 06:00:11 -05:00
|
|
|
std::fstream textStream(fullFileName, std::ios::in);
|
2015-08-18 13:01:05 -05:00
|
|
|
if (!textStream.is_open())
|
2016-03-26 07:06:39 -05:00
|
|
|
{
|
2017-03-11 13:42:50 -06:00
|
|
|
LOG_INF("Could not open " << fullFileName);
|
2017-01-17 09:42:31 -06:00
|
|
|
return false;
|
2016-03-26 07:06:39 -05:00
|
|
|
}
|
2015-03-16 14:08:07 -05:00
|
|
|
|
2016-04-22 06:00:11 -05:00
|
|
|
std::vector<char> buffer;
|
2015-08-18 13:01:05 -05:00
|
|
|
textStream.seekg(0, std::ios_base::end);
|
|
|
|
std::streamsize size = textStream.tellg();
|
2016-04-22 06:00:11 -05:00
|
|
|
buffer.resize(size);
|
2015-08-18 13:01:05 -05:00
|
|
|
textStream.seekg(0, std::ios_base::beg);
|
2016-04-22 06:00:11 -05:00
|
|
|
textStream.read(buffer.data(), size);
|
2015-08-18 13:01:05 -05:00
|
|
|
textStream.close();
|
2015-03-13 07:17:51 -05:00
|
|
|
|
2016-04-22 06:00:11 -05:00
|
|
|
if (buffer.size() > 0 && buffer.back() == '\n')
|
|
|
|
buffer.pop_back();
|
|
|
|
|
2017-01-17 09:42:31 -06:00
|
|
|
content = std::string(buffer.data(), buffer.size());
|
2017-03-11 13:42:50 -06:00
|
|
|
LOG_INF("Read '" << LOOLProtocol::getAbbreviatedMessage(content.c_str(), content.size()) <<
|
|
|
|
"' from " << fullFileName);
|
2015-03-16 14:08:07 -05:00
|
|
|
|
2017-01-17 09:42:31 -06:00
|
|
|
return true;
|
2015-03-13 07:17:51 -05:00
|
|
|
}
|
|
|
|
|
2016-04-22 06:00:11 -05:00
|
|
|
void TileCache::saveTextFile(const std::string& text, const std::string& fileName)
|
2015-06-24 15:05:49 -05:00
|
|
|
{
|
2016-04-22 06:00:11 -05:00
|
|
|
const std::string fullFileName = _cacheDir + "/" + fileName;
|
|
|
|
std::fstream textStream(fullFileName, std::ios::out);
|
2015-06-24 15:05:49 -05:00
|
|
|
|
2016-04-22 06:00:11 -05:00
|
|
|
if (!textStream.is_open())
|
2016-03-26 07:06:39 -05:00
|
|
|
{
|
2017-03-11 13:42:50 -06:00
|
|
|
LOG_ERR("Could not save '" << text << "' to " << fullFileName);
|
2016-04-22 06:00:11 -05:00
|
|
|
return;
|
2016-04-01 10:52:17 -05:00
|
|
|
}
|
2016-04-22 06:00:11 -05:00
|
|
|
else
|
2016-04-01 10:52:17 -05:00
|
|
|
{
|
2017-03-11 13:42:50 -06:00
|
|
|
LOG_INF("Saving '" << LOOLProtocol::getAbbreviatedMessage(text.c_str(), text.size()) <<
|
|
|
|
"' to " << fullFileName);
|
2016-03-26 07:06:39 -05:00
|
|
|
}
|
2015-06-24 15:05:49 -05:00
|
|
|
|
2016-04-22 06:00:11 -05:00
|
|
|
textStream << text << std::endl;
|
|
|
|
textStream.close();
|
2015-06-24 15:05:49 -05:00
|
|
|
}
|
|
|
|
|
2016-04-22 06:00:11 -05:00
|
|
|
void TileCache::setUnsavedChanges(bool state)
|
2015-03-13 07:17:51 -05:00
|
|
|
{
|
2016-04-22 06:00:11 -05:00
|
|
|
if (state)
|
|
|
|
saveTextFile("1", "unsaved.txt");
|
|
|
|
else
|
|
|
|
removeFile("unsaved.txt");
|
2015-03-13 07:17:51 -05:00
|
|
|
}
|
|
|
|
|
2015-11-27 08:12:44 -06:00
|
|
|
void TileCache::saveRendering(const std::string& name, const std::string& dir, const char *data, size_t size)
|
|
|
|
{
|
|
|
|
// can fonts be invalidated?
|
2016-04-22 06:00:11 -05:00
|
|
|
const std::string dirName = _cacheDir + "/" + dir;
|
2015-11-27 08:12:44 -06:00
|
|
|
|
|
|
|
File(dirName).createDirectories();
|
|
|
|
|
2016-03-26 07:06:39 -05:00
|
|
|
const std::string fileName = dirName + "/" + name;
|
2015-11-27 08:12:44 -06:00
|
|
|
|
2016-11-12 15:38:13 -06:00
|
|
|
FileUtil::saveDataToFileSafely(fileName, data, size);
|
2015-11-27 08:12:44 -06:00
|
|
|
}
|
|
|
|
|
2016-10-24 07:54:37 -05:00
|
|
|
std::unique_ptr<std::fstream> TileCache::lookupCachedFile(const std::string& name, const std::string& dir)
|
2015-11-27 08:12:44 -06:00
|
|
|
{
|
2016-04-22 06:00:11 -05:00
|
|
|
const std::string dirName = _cacheDir + "/" + dir;
|
2016-03-26 07:06:39 -05:00
|
|
|
const std::string fileName = dirName + "/" + name;
|
2015-11-27 08:12:44 -06:00
|
|
|
File directory(dirName);
|
|
|
|
|
|
|
|
if (directory.exists() && directory.isDirectory() && File(fileName).exists())
|
|
|
|
{
|
|
|
|
std::unique_ptr<std::fstream> result(new std::fstream(fileName, std::ios::in));
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
|
2015-05-28 10:39:05 -05:00
|
|
|
void TileCache::invalidateTiles(int part, int x, int y, int width, int height)
|
|
|
|
{
|
2017-03-11 13:42:50 -06:00
|
|
|
LOG_TRC("Removing invalidated tiles: part: " << part <<
|
|
|
|
", x: " << x << ", y: " << y <<
|
|
|
|
", width: " << width <<
|
|
|
|
", height: " << height);
|
2016-04-14 19:08:25 -05:00
|
|
|
|
2016-04-22 06:00:11 -05:00
|
|
|
File dir(_cacheDir);
|
2016-05-22 13:31:18 -05:00
|
|
|
|
2017-06-22 07:58:50 -05:00
|
|
|
assertCorrectThread();
|
2016-05-22 13:31:18 -05:00
|
|
|
|
2016-04-22 06:00:11 -05:00
|
|
|
if (dir.exists() && dir.isDirectory())
|
2015-05-28 10:39:05 -05:00
|
|
|
{
|
2016-04-22 06:00:11 -05:00
|
|
|
for (auto tileIterator = DirectoryIterator(dir); tileIterator != DirectoryIterator(); ++tileIterator)
|
2015-05-28 10:39:05 -05:00
|
|
|
{
|
2015-12-25 18:37:44 -06:00
|
|
|
const std::string fileName = tileIterator.path().getFileName();
|
2015-06-24 15:05:49 -05:00
|
|
|
if (intersectsTile(fileName, part, x, y, width, height))
|
2015-05-28 10:39:05 -05:00
|
|
|
{
|
2017-03-11 13:42:50 -06:00
|
|
|
LOG_DBG("Removing tile: " << tileIterator.path().toString());
|
2016-11-12 15:38:13 -06:00
|
|
|
FileUtil::removeFile(tileIterator.path());
|
2015-05-28 10:39:05 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-07-21 06:53:53 -05:00
|
|
|
void TileCache::invalidateTiles(const std::string& tiles)
|
2018-05-31 13:20:09 -05:00
|
|
|
{
|
|
|
|
std::pair<int, Util::Rectangle> result = TileCache::parseInvalidateMsg(tiles);
|
|
|
|
Util::Rectangle& invalidateRect = result.second;
|
|
|
|
invalidateTiles(result.first, invalidateRect.getLeft(), invalidateRect.getTop(), invalidateRect.getWidth(), invalidateRect.getHeight());
|
|
|
|
}
|
|
|
|
|
|
|
|
std::pair<int, Util::Rectangle> TileCache::parseInvalidateMsg(const std::string& tiles)
|
2015-05-28 10:39:05 -05:00
|
|
|
{
|
2015-05-29 00:49:49 -05:00
|
|
|
StringTokenizer tokens(tiles, " ", StringTokenizer::TOK_IGNORE_EMPTY | StringTokenizer::TOK_TRIM);
|
2015-05-28 10:39:05 -05:00
|
|
|
|
2015-05-28 15:22:27 -05:00
|
|
|
assert(tokens[0] == "invalidatetiles:");
|
2015-05-28 10:39:05 -05:00
|
|
|
|
2015-05-29 00:39:21 -05:00
|
|
|
if (tokens.count() == 2 && tokens[1] == "EMPTY")
|
|
|
|
{
|
2018-05-31 13:20:09 -05:00
|
|
|
return std::pair<int, Util::Rectangle>(-1, Util::Rectangle(0, 0, INT_MAX, INT_MAX));
|
2016-10-20 09:58:58 -05:00
|
|
|
}
|
|
|
|
else if (tokens.count() == 3 && tokens[1] == "EMPTY,")
|
|
|
|
{
|
|
|
|
int part = 0;
|
|
|
|
if (stringToInteger(tokens[2], part))
|
|
|
|
{
|
2018-05-31 13:20:09 -05:00
|
|
|
return std::pair<int, Util::Rectangle>(part, Util::Rectangle(0, 0, INT_MAX, INT_MAX));
|
2016-10-20 09:58:58 -05:00
|
|
|
}
|
2015-05-29 00:39:21 -05:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2015-07-21 06:53:53 -05:00
|
|
|
int part, x, y, width, height;
|
2016-10-02 14:30:59 -05:00
|
|
|
if (tokens.count() == 6 &&
|
|
|
|
getTokenInteger(tokens[1], "part", part) &&
|
2015-07-21 06:53:53 -05:00
|
|
|
getTokenInteger(tokens[2], "x", x) &&
|
|
|
|
getTokenInteger(tokens[3], "y", y) &&
|
|
|
|
getTokenInteger(tokens[4], "width", width) &&
|
|
|
|
getTokenInteger(tokens[5], "height", height))
|
|
|
|
{
|
2018-05-31 13:20:09 -05:00
|
|
|
|
|
|
|
return std::pair<int, Util::Rectangle>(part, Util::Rectangle(x, y, width, height));
|
2016-10-02 14:30:59 -05:00
|
|
|
}
|
2015-05-29 00:39:21 -05:00
|
|
|
}
|
2016-10-20 09:58:58 -05:00
|
|
|
|
2016-11-29 21:59:47 -06:00
|
|
|
LOG_ERR("Unexpected invalidatetiles request [" << tiles << "].");
|
2018-05-31 13:20:09 -05:00
|
|
|
return std::pair<int, Util::Rectangle>(-1, Util::Rectangle(0, 0, 0, 0));
|
2015-05-28 10:39:05 -05:00
|
|
|
}
|
|
|
|
|
2016-04-22 01:38:59 -05:00
|
|
|
void TileCache::removeFile(const std::string& fileName)
|
2016-02-10 11:51:24 -06:00
|
|
|
{
|
2016-04-22 08:36:09 -05:00
|
|
|
const std::string fullFileName = _cacheDir + "/" + fileName;
|
|
|
|
|
|
|
|
if (std::remove(fullFileName.c_str()) == 0)
|
2017-03-11 13:42:50 -06:00
|
|
|
LOG_INF("Removed file: " << fullFileName);
|
2015-06-24 15:05:49 -05:00
|
|
|
}
|
|
|
|
|
2016-05-15 17:47:08 -05:00
|
|
|
std::string TileCache::cacheFileName(const TileDesc& tile)
|
2015-03-12 18:34:42 -05:00
|
|
|
{
|
2016-03-26 08:16:23 -05:00
|
|
|
std::ostringstream oss;
|
2016-05-15 17:47:08 -05:00
|
|
|
oss << tile.getPart() << '_' << tile.getWidth() << 'x' << tile.getHeight() << '.'
|
|
|
|
<< tile.getTilePosX() << ',' << tile.getTilePosY() << '.'
|
|
|
|
<< tile.getTileWidth() << 'x' << tile.getTileHeight() << ".png";
|
2016-03-26 08:16:23 -05:00
|
|
|
return oss.str();
|
2015-03-12 18:34:42 -05:00
|
|
|
}
|
|
|
|
|
2016-12-23 00:42:35 -06:00
|
|
|
bool TileCache::parseCacheFileName(const std::string& fileName, int& part, int& width, int& height, int& tilePosX, int& tilePosY, int& tileWidth, int& tileHeight)
|
2015-05-28 10:39:05 -05:00
|
|
|
{
|
2017-03-30 04:15:28 -05:00
|
|
|
return std::sscanf(fileName.c_str(), "%d_%dx%d.%d,%d.%dx%d.png", &part, &width, &height, &tilePosX, &tilePosY, &tileWidth, &tileHeight) == 7;
|
2015-06-24 15:05:49 -05:00
|
|
|
}
|
|
|
|
|
2016-12-23 00:42:35 -06:00
|
|
|
bool TileCache::intersectsTile(const std::string& fileName, int part, int x, int y, int width, int height)
|
2015-06-24 15:05:49 -05:00
|
|
|
{
|
|
|
|
int tilePart, tilePixelWidth, tilePixelHeight, tilePosX, tilePosY, tileWidth, tileHeight;
|
|
|
|
if (parseCacheFileName(fileName, tilePart, tilePixelWidth, tilePixelHeight, tilePosX, tilePosY, tileWidth, tileHeight))
|
|
|
|
{
|
|
|
|
if (part != -1 && tilePart != part)
|
|
|
|
return false;
|
|
|
|
|
2016-04-14 19:08:25 -05:00
|
|
|
const int left = std::max(x, tilePosX);
|
|
|
|
const int right = std::min(x + width, tilePosX + tileWidth);
|
|
|
|
const int top = std::max(y, tilePosY);
|
|
|
|
const int bottom = std::min(y + height, tilePosY + tileHeight);
|
2015-06-24 15:05:49 -05:00
|
|
|
|
|
|
|
if (left <= right && top <= bottom)
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
2015-05-28 10:39:05 -05:00
|
|
|
}
|
|
|
|
|
2015-05-29 00:49:49 -05:00
|
|
|
Timestamp TileCache::getLastModified()
|
2015-03-12 18:34:42 -05:00
|
|
|
{
|
2016-04-22 06:00:11 -05:00
|
|
|
std::fstream modTimeFile(_cacheDir + "/modtime.txt", std::ios::in);
|
2015-03-12 18:34:42 -05:00
|
|
|
|
|
|
|
if (!modTimeFile.is_open())
|
|
|
|
return 0;
|
|
|
|
|
2015-05-29 00:49:49 -05:00
|
|
|
Timestamp::TimeVal result;
|
2015-03-12 18:34:42 -05:00
|
|
|
modTimeFile >> result;
|
|
|
|
|
|
|
|
modTimeFile.close();
|
|
|
|
return result;
|
2015-03-12 09:18:35 -05:00
|
|
|
}
|
|
|
|
|
2016-04-01 10:56:08 -05:00
|
|
|
void TileCache::saveLastModified(const Timestamp& timestamp)
|
2015-08-04 13:37:05 -05:00
|
|
|
{
|
2016-04-22 06:00:11 -05:00
|
|
|
std::fstream modTimeFile(_cacheDir + "/modtime.txt", std::ios::out);
|
2015-08-04 13:37:05 -05:00
|
|
|
modTimeFile << timestamp.raw() << std::endl;
|
|
|
|
modTimeFile.close();
|
|
|
|
}
|
|
|
|
|
2016-04-25 04:04:25 -05:00
|
|
|
// FIXME: to be further simplified when we centralize tile messages.
|
2016-12-12 19:18:25 -06:00
|
|
|
void TileCache::subscribeToTileRendering(const TileDesc& tile, const std::shared_ptr<ClientSession>& subscriber)
|
2016-04-23 11:11:11 -05:00
|
|
|
{
|
2016-09-19 19:51:10 -05:00
|
|
|
std::ostringstream oss;
|
|
|
|
oss << '(' << tile.getPart() << ',' << tile.getTilePosX() << ',' << tile.getTilePosY() << ')';
|
2018-02-07 03:17:59 -06:00
|
|
|
const std::string name = oss.str();
|
2016-09-19 19:51:10 -05:00
|
|
|
|
2017-06-22 07:58:50 -05:00
|
|
|
assertCorrectThread();
|
2016-10-30 14:24:27 -05:00
|
|
|
|
|
|
|
std::shared_ptr<TileBeingRendered> tileBeingRendered = findTileBeingRendered(tile);
|
|
|
|
|
2016-04-23 11:11:11 -05:00
|
|
|
if (tileBeingRendered)
|
|
|
|
{
|
2016-04-25 04:04:25 -05:00
|
|
|
for (const auto &s : tileBeingRendered->_subscribers)
|
|
|
|
{
|
|
|
|
if (s.lock().get() == subscriber.get())
|
|
|
|
{
|
2017-03-11 13:42:50 -06:00
|
|
|
LOG_DBG("Redundant request to subscribe on tile " << name);
|
2016-09-21 17:48:14 -05:00
|
|
|
tileBeingRendered->setVersion(tile.getVersion());
|
2016-09-25 15:04:27 -05:00
|
|
|
return;
|
2016-04-25 04:04:25 -05:00
|
|
|
}
|
|
|
|
}
|
2016-09-19 19:51:10 -05:00
|
|
|
|
2017-03-11 13:42:50 -06:00
|
|
|
LOG_DBG("Subscribing " << subscriber->getName() << " to tile " << name << " which has " <<
|
|
|
|
tileBeingRendered->_subscribers.size() << " subscribers already.");
|
2016-04-25 04:04:25 -05:00
|
|
|
tileBeingRendered->_subscribers.push_back(subscriber);
|
2018-07-23 09:09:55 -05:00
|
|
|
if(tile.getId() == -1)
|
|
|
|
subscriber->traceSubscribe();
|
2016-04-25 04:04:25 -05:00
|
|
|
|
2016-05-15 09:19:49 -05:00
|
|
|
const auto duration = (std::chrono::steady_clock::now() - tileBeingRendered->getStartTime());
|
|
|
|
if (std::chrono::duration_cast<std::chrono::milliseconds>(duration).count() > COMMAND_TIMEOUT_MS)
|
|
|
|
{
|
|
|
|
// Tile painting has stalled. Reissue.
|
2016-09-21 17:48:14 -05:00
|
|
|
tileBeingRendered->setVersion(tile.getVersion());
|
2016-05-15 09:19:49 -05:00
|
|
|
}
|
2016-04-23 11:11:11 -05:00
|
|
|
}
|
2016-04-25 04:04:25 -05:00
|
|
|
else
|
|
|
|
{
|
2017-03-11 13:42:50 -06:00
|
|
|
LOG_DBG("Subscribing " << subscriber->getName() << " to tile " << name <<
|
|
|
|
" ver=" << tile.getVersion() << " which has no subscribers.");
|
2016-05-04 21:05:09 -05:00
|
|
|
|
2016-05-15 17:47:08 -05:00
|
|
|
const std::string cachedName = cacheFileName(tile);
|
2016-04-23 11:11:11 -05:00
|
|
|
|
2016-04-25 04:04:25 -05:00
|
|
|
assert(_tilesBeingRendered.find(cachedName) == _tilesBeingRendered.end());
|
|
|
|
|
2016-09-21 17:47:00 -05:00
|
|
|
tileBeingRendered = std::make_shared<TileBeingRendered>(cachedName, tile);
|
2016-05-01 19:50:11 -05:00
|
|
|
tileBeingRendered->_subscribers.push_back(subscriber);
|
2018-07-23 09:09:55 -05:00
|
|
|
if(tile.getId() == -1)
|
|
|
|
subscriber->traceSubscribe();
|
2016-05-01 19:50:11 -05:00
|
|
|
_tilesBeingRendered[cachedName] = tileBeingRendered;
|
2016-04-25 04:04:25 -05:00
|
|
|
}
|
2016-04-23 11:11:11 -05:00
|
|
|
}
|
|
|
|
|
2016-09-19 21:16:45 -05:00
|
|
|
std::string TileCache::cancelTiles(const std::shared_ptr<ClientSession> &subscriber)
|
2016-08-30 22:15:44 -05:00
|
|
|
{
|
2016-09-20 18:20:23 -05:00
|
|
|
assert(subscriber && "cancelTiles expects valid subscriber");
|
2017-03-11 13:42:50 -06:00
|
|
|
LOG_TRC("Cancelling tiles for " << subscriber->getName());
|
2016-09-20 18:20:23 -05:00
|
|
|
|
2017-06-22 07:58:50 -05:00
|
|
|
assertCorrectThread();
|
2016-08-30 22:15:44 -05:00
|
|
|
|
2018-07-31 06:03:05 -05:00
|
|
|
ClientSession* sub = subscriber.get();
|
2016-08-30 22:15:44 -05:00
|
|
|
|
2016-09-19 21:16:45 -05:00
|
|
|
std::ostringstream oss;
|
2016-08-30 22:15:44 -05:00
|
|
|
|
|
|
|
for (auto it = _tilesBeingRendered.begin(); it != _tilesBeingRendered.end(); )
|
|
|
|
{
|
2016-10-04 05:20:41 -05:00
|
|
|
if (it->second->getTile().getId() >= 0)
|
|
|
|
{
|
|
|
|
// Tile is for a thumbnail, don't cancel it
|
|
|
|
++it;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2016-08-30 22:15:44 -05:00
|
|
|
auto& subscribers = it->second->_subscribers;
|
2017-03-11 13:42:50 -06:00
|
|
|
LOG_TRC("Tile " << it->first << " has " << subscribers.size() << " subscribers.");
|
2016-08-30 22:15:44 -05:00
|
|
|
|
2016-09-19 21:16:45 -05:00
|
|
|
const auto itRem = std::find_if(subscribers.begin(), subscribers.end(),
|
|
|
|
[sub](std::weak_ptr<ClientSession>& ptr){ return ptr.lock().get() == sub; });
|
|
|
|
if (itRem != subscribers.end())
|
|
|
|
{
|
2017-03-11 13:42:50 -06:00
|
|
|
LOG_TRC("Tile " << it->first << " has " << subscribers.size() <<
|
|
|
|
" subscribers. Removing " << subscriber->getName() << ".");
|
2016-09-19 21:16:45 -05:00
|
|
|
subscribers.erase(itRem, itRem + 1);
|
|
|
|
if (subscribers.empty())
|
|
|
|
{
|
|
|
|
// No other subscriber, remove it from the render queue.
|
|
|
|
oss << it->second->getVersion() << ',';
|
|
|
|
it = _tilesBeingRendered.erase(it);
|
2016-09-21 09:38:23 -05:00
|
|
|
continue;
|
2016-09-19 21:16:45 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-09-21 09:38:23 -05:00
|
|
|
++it;
|
2016-08-30 22:15:44 -05:00
|
|
|
}
|
2016-09-19 21:16:45 -05:00
|
|
|
|
2018-07-31 06:03:05 -05:00
|
|
|
if(sub)
|
|
|
|
sub->clearSubscription();
|
2018-02-07 03:17:59 -06:00
|
|
|
const std::string canceltiles = oss.str();
|
2017-03-30 04:15:28 -05:00
|
|
|
return canceltiles.empty() ? canceltiles : "canceltiles " + canceltiles;
|
2016-08-30 22:15:44 -05:00
|
|
|
}
|
|
|
|
|
2017-06-22 07:58:50 -05:00
|
|
|
void TileCache::assertCorrectThread()
|
|
|
|
{
|
|
|
|
const bool correctThread = _owner == std::thread::id(0) || std::this_thread::get_id() == _owner;
|
|
|
|
if (!correctThread)
|
2018-01-22 08:11:43 -06:00
|
|
|
LOG_ERR("TileCache method invoked from foreign thread. Expected: " <<
|
|
|
|
Log::to_string(_owner) << " but called from " <<
|
|
|
|
std::this_thread::get_id() << " (" << Util::getThreadId() << ").");
|
2017-06-22 07:58:50 -05:00
|
|
|
assert (correctThread);
|
|
|
|
}
|
|
|
|
|
2015-03-12 09:18:35 -05:00
|
|
|
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
|