libreoffice-online/loolwsd/TileCache.cpp
Ashod Nakashian 50e6272440 Fixes to get a consistently working state
Fixed a javascript error and a more serious referencing
of the memory of a temp string, which resulted in
random behavior.

When the memory was null, it failed to load any
documents in the browser. Otherwise, accidentally,
when it wasn't, it would load the document (rarely).
2015-12-10 21:32:16 +01:00

423 lines
13 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/.
*/
#include "config.h"
#include <cassert>
#include <climits>
#include <cstdio>
#include <fstream>
#include <iostream>
#include <memory>
#include <sstream>
#include <string>
#include <Poco/DigestEngine.h>
#include <Poco/DirectoryIterator.h>
#include <Poco/Exception.h>
#include <Poco/File.h>
#include <Poco/Path.h>
#include <Poco/SHA1Engine.h>
#include <Poco/StringTokenizer.h>
#include <Poco/Timestamp.h>
#include <Poco/URI.h>
#include <Poco/Util/Application.h>
#include "LOOLWSD.hpp"
#include "LOOLProtocol.hpp"
#include "TileCache.hpp"
#include "Util.hpp"
using Poco::DigestEngine;
using Poco::DirectoryIterator;
using Poco::File;
using Poco::SHA1Engine;
using Poco::StringTokenizer;
using Poco::SyntaxException;
using Poco::Timestamp;
using Poco::URI;
using Poco::Util::Application;
using namespace LOOLProtocol;
TileCache::TileCache(const std::string& docURL, const std::string& timestamp) :
_docURL(docURL),
_isEditing(false),
_hasUnsavedChanges(false)
{
setup(timestamp);
}
std::unique_ptr<std::fstream> TileCache::lookupTile(int part, int width, int height, int tilePosX, int tilePosY, int tileWidth, int tileHeight)
{
std::string cachedName = cacheFileName(part, width, height, tilePosX, tilePosY, tileWidth, tileHeight);
if (_hasUnsavedChanges)
{
// try the Editing cache first
std::string dirName = cacheDirName(true);
std::string fileName = + "/" + cachedName;
File dir(dirName);
if (dir.exists() && dir.isDirectory() && File(fileName).exists())
{
std::unique_ptr<std::fstream> result(new std::fstream(fileName, std::ios::in));
return result;
}
}
// skip tiles scheduled for removal from the Persistent cache (on save)
if (_toBeRemoved.find(cachedName) != _toBeRemoved.end())
return nullptr;
// default to the content of the Persistent cache
std::string dirName = cacheDirName(false);
File dir(dirName);
if (!dir.exists() || !dir.isDirectory())
return nullptr;
std::string fileName = dirName + "/" + cachedName;
std::unique_ptr<std::fstream> result(new std::fstream(fileName, std::ios::in));
return result;
}
void TileCache::saveTile(int part, int width, int height, int tilePosX, int tilePosY, int tileWidth, int tileHeight, const char *data, size_t size)
{
if (_isEditing && !_hasUnsavedChanges)
_hasUnsavedChanges = true;
std::string dirName = cacheDirName(_hasUnsavedChanges);
File(dirName).createDirectories();
std::string fileName = dirName + "/" + cacheFileName(part, width, height, tilePosX, tilePosY, tileWidth, tileHeight);
std::fstream outStream(fileName, std::ios::out);
outStream.write(data, size);
outStream.close();
}
std::string TileCache::getTextFile(std::string fileName)
{
const auto textFile = std::string("/" + fileName);
std::string dirName = cacheDirName(false);
if (_hasUnsavedChanges)
{
// try the Editing cache first, and prefer it if it exists
std::string editingDirName = cacheDirName(true);
File dir(editingDirName);
File text(editingDirName + textFile);
if (dir.exists() && dir.isDirectory() && text.exists() && !text.isDirectory())
dirName = editingDirName;
}
if (!File(dirName).exists() || !File(dirName).isDirectory())
return "";
fileName = dirName + textFile;
std::fstream textStream(fileName, std::ios::in);
if (!textStream.is_open())
return "";
std::vector<char> result;
textStream.seekg(0, std::ios_base::end);
std::streamsize size = textStream.tellg();
result.resize(size);
textStream.seekg(0, std::ios_base::beg);
textStream.read(result.data(), size);
textStream.close();
if (result[result.size()-1] == '\n')
result.resize(result.size() - 1);
return std::string(result.data(), result.size());
}
void TileCache::documentSaved()
{
// first remove the invalidated tiles from the Persistent cache
std::string persistentDirName = cacheDirName(false);
for (const auto& it : _toBeRemoved)
File(persistentDirName + "/" + it).remove();
_cacheMutex.lock();
// then move the new tiles from the Editing cache to Persistent
for (auto tileIterator = DirectoryIterator(cacheDirName(true)); tileIterator != DirectoryIterator(); ++tileIterator)
tileIterator->moveTo(persistentDirName);
_cacheMutex.unlock();
// update status
_toBeRemoved.clear();
_hasUnsavedChanges = false;
// FIXME should we take the exact time of the file for the local files?
saveLastModified(Timestamp());
}
void TileCache::setEditing(bool editing)
{
_isEditing = editing;
}
void TileCache::saveTextFile(const std::string& text, std::string fileName)
{
std::string dirName = cacheDirName(_isEditing);
File(dirName).createDirectories();
StringTokenizer tokens(text, " ", StringTokenizer::TOK_IGNORE_EMPTY | StringTokenizer::TOK_TRIM);
fileName = dirName + "/" + fileName;
std::fstream textStream(fileName, std::ios::out);
if (!textStream.is_open())
return;
textStream << text << std::endl;
textStream.close();
}
void TileCache::saveRendering(const std::string& name, const std::string& dir, const char *data, size_t size)
{
// can fonts be invalidated?
std::string dirName = cacheDirName(false) + "/" + dir;
File(dirName).createDirectories();
std::string fileName = dirName + "/" + name;
std::fstream outStream(fileName, std::ios::out);
outStream.write(data, size);
outStream.close();
}
std::unique_ptr<std::fstream> TileCache::lookupRendering(const std::string& name, const std::string& dir)
{
std::string dirName = cacheDirName(false) + "/" + dir;
std::string fileName = dirName + "/" + name;
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;
}
void TileCache::invalidateTiles(int part, int x, int y, int width, int height)
{
// in the Editing cache, remove immediately
std::string editingDirName = cacheDirName(true);
File editingDir(editingDirName);
if (editingDir.exists() && editingDir.isDirectory())
{
_cacheMutex.lock();
for (auto tileIterator = DirectoryIterator(editingDir); tileIterator != DirectoryIterator(); ++tileIterator)
{
std::string fileName = tileIterator.path().getFileName();
if (intersectsTile(fileName, part, x, y, width, height))
{
File(tileIterator.path()).remove();
}
}
_cacheMutex.unlock();
}
// in the Persistent cache, add to _toBeRemoved for removal on save
std::string persistentDirName = cacheDirName(false);
File persistentDir(persistentDirName);
if (persistentDir.exists() && persistentDir.isDirectory())
{
for (auto tileIterator = DirectoryIterator(persistentDir); tileIterator != DirectoryIterator(); ++tileIterator)
{
std::string fileName = tileIterator.path().getFileName();
if (_toBeRemoved.find(fileName) == _toBeRemoved.end() && intersectsTile(fileName, part, x, y, width, height))
{
_toBeRemoved.insert(fileName);
}
}
}
}
void TileCache::invalidateTiles(const std::string& tiles)
{
StringTokenizer tokens(tiles, " ", StringTokenizer::TOK_IGNORE_EMPTY | StringTokenizer::TOK_TRIM);
assert(tokens[0] == "invalidatetiles:");
if (tokens.count() == 2 && tokens[1] == "EMPTY")
{
invalidateTiles(-1, 0, 0, INT_MAX, INT_MAX);
}
else if (tokens.count() != 6)
{
return;
}
else
{
int part, x, y, width, height;
if (getTokenInteger(tokens[1], "part", part) &&
getTokenInteger(tokens[2], "x", x) &&
getTokenInteger(tokens[3], "y", y) &&
getTokenInteger(tokens[4], "width", width) &&
getTokenInteger(tokens[5], "height", height))
{
invalidateTiles(part, x, y, width, height);
}
}
}
std::string TileCache::toplevelCacheDirName()
{
SHA1Engine digestEngine;
digestEngine.update(_docURL.c_str(), _docURL.size());
return (LOOLWSD::cache + "/" +
DigestEngine::digestToHex(digestEngine.digest()).insert(3, "/").insert(2, "/").insert(1, "/"));
}
std::string TileCache::cacheDirName(bool useEditingCache)
{
if (useEditingCache)
return toplevelCacheDirName() + "/editing";
else
return toplevelCacheDirName() + "/persistent";
}
std::string TileCache::cacheFileName(int part, int width, int height, int tilePosX, int tilePosY, int tileWidth, int tileHeight)
{
return (std::to_string(part) + "_" +
std::to_string(width) + "x" + std::to_string(height) + "." +
std::to_string(tilePosX) + "," + std::to_string(tilePosY) + "." +
std::to_string(tileWidth) + "x" + std::to_string(tileHeight) + ".png");
}
bool TileCache::parseCacheFileName(std::string& fileName, int& part, int& width, int& height, int& tilePosX, int& tilePosY, int& tileWidth, int& tileHeight)
{
return (std::sscanf(fileName.c_str(), "%d_%dx%d.%d,%d.%dx%d.png", &part, &width, &height, &tilePosX, &tilePosY, &tileWidth, &tileHeight) == 7);
}
bool TileCache::intersectsTile(std::string& fileName, int part, int x, int y, int width, int height)
{
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;
int left = std::max(x, tilePosX);
int right = std::min(x + width, tilePosX + tileWidth);
int top = std::max(y, tilePosY);
int bottom = std::min(y + height, tilePosY + tileHeight);
if (left <= right && top <= bottom)
return true;
}
return false;
}
Timestamp TileCache::getLastModified()
{
std::fstream modTimeFile(toplevelCacheDirName() + "/modtime.txt", std::ios::in);
if (!modTimeFile.is_open())
return 0;
Timestamp::TimeVal result;
modTimeFile >> result;
modTimeFile.close();
return result;
}
void TileCache::saveLastModified(const Poco::Timestamp& timestamp)
{
std::fstream modTimeFile(toplevelCacheDirName() + "/modtime.txt", std::ios::out);
modTimeFile << timestamp.raw() << std::endl;
modTimeFile.close();
}
void TileCache::setup(const std::string& timestamp)
{
bool cleanEverything = true;
std::string filePath;
Timestamp lastModified;
try
{
URI uri(_docURL);
if (uri.getScheme() == "" ||
uri.getScheme() == "file")
{
filePath = uri.getPath();
}
}
catch (SyntaxException& e)
{
}
if (!filePath.empty() && File(filePath).exists() && File(filePath).isFile())
{
// for files, always use the real path
lastModified = File(filePath).getLastModified();
cleanEverything = (getLastModified() < lastModified);
}
else if (!timestamp.empty())
{
// otherwise try the timestamp provided by the caller
Timestamp::TimeVal lastTimeVal;
std::istringstream(timestamp) >> lastTimeVal;
lastModified = lastTimeVal;
Application::instance().logger().information(Util::logPrefix() + "Timestamp provided externally: " + timestamp);
cleanEverything = (getLastModified() < Timestamp(lastModified));
}
else
{
// when no timestamp, and non-file, assume 'now'
lastModified = Timestamp();
}
File cacheDir(toplevelCacheDirName());
if (cacheDir.exists())
{
if (cleanEverything)
{
// document changed externally, clean up everything
cacheDir.remove(true);
Application::instance().logger().information(Util::logPrefix() + "Completely cleared cache: " + toplevelCacheDirName());
}
else
{
// remove only the Editing cache
File editingCacheDir(cacheDirName(true));
if (editingCacheDir.exists())
{
editingCacheDir.remove(true);
Application::instance().logger().information(Util::logPrefix() + "Cleared the editing cache: " + cacheDirName(true));
}
}
}
cacheDir.createDirectories();
saveLastModified(lastModified);
}
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */