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/.
*/
# include "config.h"
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>
2016-04-04 19:59:46 -05:00
# include <mutex>
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
2016-03-25 21:56:18 -05:00
# include "Storage.hpp"
2015-07-21 06:53:53 -05:00
# include "LOOLProtocol.hpp"
2015-03-12 09:18:35 -05:00
# include "TileCache.hpp"
2015-08-04 13:37:05 -05:00
# include "Util.hpp"
2016-04-23 11:11:11 -05:00
# include "MasterProcessSession.hpp"
2015-03-12 09:18:35 -05:00
2015-05-29 00:49:49 -05:00
using Poco : : DirectoryIterator ;
using Poco : : File ;
2016-04-01 10:52:17 -05:00
using Poco : : FileException ;
2016-04-01 10:56:08 -05:00
using Poco : : Path ;
2015-05-29 00:49:49 -05:00
using Poco : : StringTokenizer ;
using Poco : : Timestamp ;
2015-05-29 04:23:57 -05:00
using Poco : : URI ;
2015-05-29 00:49:49 -05:00
2015-07-21 06:53:53 -05:00
using namespace LOOLProtocol ;
2016-04-23 10:53:03 -05:00
TileBeingRendered : : TileBeingRendered ( )
{
_startTime . update ( ) ;
}
2016-04-22 01:38:59 -05:00
void TileBeingRendered : : subscribe ( const std : : weak_ptr < MasterProcessSession > & session )
2016-04-15 10:24:00 -05:00
{
2016-04-22 01:58:31 -05:00
std : : shared_ptr < MasterProcessSession > cmp = session . lock ( ) ;
2016-04-25 02:11:59 -05:00
for ( const auto & s : _subscribers )
2016-04-22 01:58:31 -05:00
{
if ( s . lock ( ) . get ( ) = = cmp . get ( ) )
{
Log : : debug ( " Redundant request to re-subscribe on a tile " ) ;
return ;
}
}
2016-04-15 10:24:00 -05:00
_subscribers . push_back ( session ) ;
}
std : : vector < std : : weak_ptr < MasterProcessSession > > TileBeingRendered : : getSubscribers ( )
{
return _subscribers ;
}
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-04-22 06:00:11 -05:00
_cacheDir ( cacheDir )
2015-03-12 18:34:42 -05:00
{
2016-04-22 06:00:11 -05:00
Log : : info ( " TileCache ctor for uri [ " + _docURL + " ] modifiedTime= " + std : : to_string ( modifiedTime . raw ( ) / 1000000 ) + " getLastModified()= " + std : : to_string ( getLastModified ( ) . raw ( ) / 1000000 ) ) ;
File directory ( _cacheDir ) ;
if ( directory . exists ( ) & &
( getLastModified ( ) < modifiedTime | |
getTextFile ( " unsaved.txt " ) ! = " " ) )
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.
Util : : removeFile ( _cacheDir , true ) ;
Log : : info ( " Completely cleared tile cache: " + _cacheDir ) ;
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 ( )
{
2016-03-29 19:56:25 -05:00
Log : : info ( " ~TileCache dtor for uri [ " + _docURL + " ]. " ) ;
2016-04-15 10:24:00 -05:00
}
void TileCache : : rememberTileAsBeingRendered ( int part , int width , int height , int tilePosX , int tilePosY , int tileWidth , int tileHeight )
{
const std : : string cachedName = cacheFileName ( part , width , height , tilePosX , tilePosY , tileWidth , tileHeight ) ;
assert ( _tilesBeingRendered . find ( cachedName ) = = _tilesBeingRendered . end ( ) ) ;
_tilesBeingRendered [ cachedName ] = std : : make_shared < TileBeingRendered > ( ) ;
}
std : : shared_ptr < TileBeingRendered > TileCache : : findTileBeingRendered ( int part , int width , int height , int tilePosX , int tilePosY , int tileWidth , int tileHeight )
{
const std : : string cachedName = cacheFileName ( part , width , height , tilePosX , tilePosY , tileWidth , tileHeight ) ;
auto tile = _tilesBeingRendered . find ( cachedName ) ;
if ( tile = = _tilesBeingRendered . end ( ) )
return nullptr ;
return tile - > second ;
}
void TileCache : : forgetTileBeingRendered ( int part , int width , int height , int tilePosX , int tilePosY , int tileWidth , int tileHeight )
{
const std : : string cachedName = cacheFileName ( part , width , height , tilePosX , tilePosY , tileWidth , tileHeight ) ;
assert ( _tilesBeingRendered . find ( cachedName ) ! = _tilesBeingRendered . end ( ) ) ;
_tilesBeingRendered . erase ( cachedName ) ;
2016-03-26 06:50:13 -05:00
}
2015-04-27 13:24:05 -05:00
std : : unique_ptr < std : : fstream > TileCache : : lookupTile ( int part , int width , int height , int tilePosX , int tilePosY , int tileWidth , int tileHeight )
2015-03-12 09:18:35 -05:00
{
2016-04-22 06:00:11 -05:00
const std : : string fileName = _cacheDir + " / " + cacheFileName ( part , width , height , tilePosX , tilePosY , tileWidth , tileHeight ) ;
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 ) ) ;
if ( result & & result - > is_open ( ) )
2016-03-26 07:20:48 -05:00
{
2016-04-22 06:00:11 -05:00
Log : : trace ( " 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
}
2015-04-27 13:24:05 -05:00
void TileCache : : saveTile ( int part , int width , int height , int tilePosX , int tilePosY , int tileWidth , int tileHeight , const char * data , size_t size )
2015-03-12 09:18:35 -05:00
{
2016-04-22 06:00:11 -05:00
const std : : string fileName = _cacheDir + " / " + cacheFileName ( part , width , height , tilePosX , tilePosY , tileWidth , tileHeight ) ;
2015-06-24 15:05:49 -05:00
2016-04-22 06:00:11 -05:00
Log : : trace ( ) < < " Saving cache tile: " < < fileName < < Log : : end ;
2015-03-13 07:17:51 -05:00
2015-03-12 18:34:42 -05:00
std : : fstream outStream ( fileName , std : : ios : : out ) ;
2015-03-12 09:18:35 -05:00
outStream . write ( data , size ) ;
outStream . close ( ) ;
2015-03-12 18:34:42 -05:00
}
2016-04-22 06:00:11 -05:00
std : : string TileCache : : getTextFile ( const std : : string & fileName )
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
{
2016-04-22 06:00:11 -05:00
Log : : info ( " Could not open " + fullFileName ) ;
2015-03-13 07:17:51 -05:00
return " " ;
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 ( ) ;
std : : string result = std : : string ( buffer . data ( ) , buffer . size ( ) ) ;
Log : : info ( " Read ' " + result + " ' from " + fullFileName ) ;
2015-03-16 14:08:07 -05:00
2016-04-22 06:00:11 -05:00
return result ;
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
{
2016-04-22 06:00:11 -05:00
Log : : error ( " Could not save ' " + text + " ' to " + fullFileName ) ;
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
{
2016-04-22 06:00:11 -05:00
Log : : info ( " Saving ' " + text + " ' 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
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 )
{
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 )
{
2016-04-14 19:08:25 -05:00
Log : : trace ( ) < < " Removing invalidated tiles: part: " < < part
< < " , x: " < < x < < " , y: " < < y
< < " , width: " < < width
< < " , height: " < < height < < Log : : end ;
2016-04-22 06:00:11 -05:00
File dir ( _cacheDir ) ;
if ( dir . exists ( ) & & dir . isDirectory ( ) )
2015-05-28 10:39:05 -05:00
{
2016-04-04 19:59:46 -05:00
std : : unique_lock < std : : mutex > lock ( _cacheMutex ) ;
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
{
2016-04-21 00:19:58 -05:00
Log : : debug ( " Removing tile: " + tileIterator . path ( ) . toString ( ) ) ;
2015-12-25 19:11:47 -06:00
Util : : 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 )
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 " )
{
invalidateTiles ( - 1 , 0 , 0 , INT_MAX , INT_MAX ) ;
}
2015-07-21 06:53:53 -05:00
else if ( tokens . count ( ) ! = 6 )
2015-05-29 00:39:21 -05:00
{
2015-05-28 10:39:05 -05:00
return ;
2015-05-29 00:39:21 -05:00
}
else
{
2015-07-21 06:53:53 -05:00
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 ) ;
}
2015-05-29 00:39:21 -05:00
}
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 )
Log : : info ( " Removed file: " + _cacheDir + " / " + fileName ) ;
2015-06-24 15:05:49 -05:00
}
2015-04-27 13:24:05 -05:00
std : : string TileCache : : cacheFileName ( int part , int width , int height , int tilePosX , int tilePosY , int tileWidth , int tileHeight )
2015-03-12 18:34:42 -05:00
{
2016-03-26 08:16:23 -05:00
std : : ostringstream oss ;
oss < < part < < ' _ ' < < width < < ' x ' < < height < < ' . '
< < tilePosX < < ' , ' < < tilePosY < < ' . '
< < tileWidth < < ' x ' < < tileHeight < < " .png " ;
return oss . str ( ) ;
2015-03-12 18:34:42 -05:00
}
2015-12-25 18:37:44 -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
{
2015-06-24 15:05:49 -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-12-25 18:37:44 -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-23 11:11:11 -05:00
void TileCache : : notifyAndRemoveSubscribers ( int part , int width , int height , int tilePosX , int tilePosY , int tileWidth , int tileHeight , MasterProcessSession * emitter )
{
std : : unique_lock < std : : mutex > lock ( _tilesBeingRenderedMutex ) ;
std : : shared_ptr < TileBeingRendered > tileBeingRendered = findTileBeingRendered ( part , width , height , tilePosX , tilePosY , tileWidth , tileHeight ) ;
if ( ! tileBeingRendered )
return ;
Log : : debug ( " Sending tile message also to subscribers " ) ;
for ( auto i : tileBeingRendered - > getSubscribers ( ) )
{
auto subscriber = i . lock ( ) ;
if ( subscriber )
{
if ( subscriber . get ( ) = = emitter )
{
Log : : error ( " Refusing to queue new tile message for ourselves " ) ;
continue ;
}
std : : shared_ptr < BasicTileQueue > queue ;
queue = subscriber - > getQueue ( ) ;
// Re-emit the tile command in the other thread(s) to re-check and hit
// the cache. Construct the message from scratch to contain only the
// mandatory parts of the message.
if ( queue )
{
const std : : string message ( " tile "
" part= " + std : : to_string ( part ) +
" width= " + std : : to_string ( width ) +
" height= " + std : : to_string ( height ) +
" tileposx= " + std : : to_string ( tilePosX ) +
" tileposy= " + std : : to_string ( tilePosY ) +
" tilewidth= " + std : : to_string ( tileWidth ) +
" tileheight= " + std : : to_string ( tileHeight ) +
" \n " ) ;
queue - > put ( message ) ;
}
}
}
forgetTileBeingRendered ( part , width , height , tilePosX , tilePosY , tileWidth , tileHeight ) ;
lock . unlock ( ) ;
}
bool TileCache : : isTileBeingRenderedIfSoSubscribe ( int part , int width , int height , int tilePosX , int tilePosY , int tileWidth , int tileHeight , const std : : shared_ptr < MasterProcessSession > & subscriber )
{
std : : unique_lock < std : : mutex > lock ( _tilesBeingRenderedMutex ) ;
std : : shared_ptr < TileBeingRendered > tileBeingRendered = findTileBeingRendered ( part , width , height , tilePosX , tilePosY , tileWidth , tileHeight ) ;
if ( tileBeingRendered )
{
Log : : debug ( " Tile is already being rendered, subscribing " ) ;
assert ( subscriber - > getKind ( ) = = LOOLSession : : Kind : : ToClient ) ;
tileBeingRendered - > subscribe ( subscriber ) ;
return true ;
}
rememberTileAsBeingRendered ( part , width , height , tilePosX , tilePosY , tileWidth , tileHeight ) ;
lock . unlock ( ) ;
return false ;
}
2015-03-12 09:18:35 -05:00
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */