mirror of
https://git.lyx.org/repos/lyx.git
synced 2024-11-06 11:23:45 +00:00
244de5d2c1
Addressing #10481. This patch adds the new 'needauth' option for converters launching external programs that are capable of running arbitrary code on behalf of the user. These converters won't be run unless the user gives explicit authorization, which is asked on-demand when the converter is about to be run (question is not asked if the file is cached and calling the converter is not needed). The user prompt has a 3rd button so that he/she's not prompted again for (any converter over) the same document (identified through buffer->absFileName()). Two preference options are added: lyxrc.use_converter_needauth_forbidden disables any converter with the 'needauth' option, which is meant to force user to an explicit action via the preferences pane, before being able to use advanced converters that can potentially bring security threats; lyxrc.use_converter_needauth enables prompting the user for 'needauth' converters, or bypasses the check if not enabled, falling back to the previous behavior. So, the first option is for maximum security, the second is for maximum usability.
510 lines
9.6 KiB
C++
510 lines
9.6 KiB
C++
/**
|
|
* \file GraphicsLoader.cpp
|
|
* This file is part of LyX, the document processor.
|
|
* Licence details can be found in the file COPYING.
|
|
*
|
|
* \author Angus Leeming
|
|
*
|
|
* Full author contact details are available in file CREDITS.
|
|
*/
|
|
|
|
#include <config.h>
|
|
|
|
#include "GraphicsLoader.h"
|
|
|
|
#include "GraphicsCacheItem.h"
|
|
#include "GraphicsImage.h"
|
|
#include "GraphicsParams.h"
|
|
#include "GraphicsCache.h"
|
|
|
|
#include "support/debug.h"
|
|
#include "support/lassert.h"
|
|
#include "support/Timeout.h"
|
|
|
|
#include "support/bind.h"
|
|
|
|
#include <queue>
|
|
#include <memory>
|
|
#include <set>
|
|
|
|
using namespace std;
|
|
using namespace lyx::support;
|
|
|
|
namespace lyx {
|
|
|
|
namespace graphics {
|
|
|
|
|
|
/////////////////////////////////////////////////////////////////////
|
|
//
|
|
// LoaderQueue
|
|
//
|
|
/////////////////////////////////////////////////////////////////////
|
|
|
|
class LoaderQueue {
|
|
public:
|
|
/// Use this to request that the item is loaded.
|
|
void touch(Cache::ItemPtr const & item);
|
|
/// Query whether the clock is ticking.
|
|
bool running() const;
|
|
///get the and only instance of the class
|
|
static LoaderQueue & get();
|
|
private:
|
|
/// This class is a singleton class... use LoaderQueue::get() instead
|
|
LoaderQueue();
|
|
/// The in-progress loading queue (elements are unique here).
|
|
list<Cache::ItemPtr> cache_queue_;
|
|
/// Used to make the insertion of new elements faster.
|
|
set<Cache::ItemPtr> cache_set_;
|
|
/// Newly touched elements go here. loadNext moves them to cache_queue_
|
|
queue<Cache::ItemPtr> bucket_;
|
|
///
|
|
Timeout timer;
|
|
///
|
|
bool running_;
|
|
|
|
/** This is the 'threaded' method, that does the loading in the
|
|
* background.
|
|
*/
|
|
void loadNext();
|
|
///
|
|
void startLoader();
|
|
///
|
|
void stopLoader();
|
|
};
|
|
|
|
|
|
|
|
//static int const s_numimages_ = 5;
|
|
static int const s_numimages_ = 10;
|
|
static int const s_millisecs_ = 500;
|
|
|
|
|
|
LoaderQueue & LoaderQueue::get()
|
|
{
|
|
static LoaderQueue singleton;
|
|
return singleton;
|
|
}
|
|
|
|
|
|
void LoaderQueue::loadNext()
|
|
{
|
|
LYXERR(Debug::GRAPHICS, "LoaderQueue: "
|
|
<< cache_queue_.size() << " items in the queue");
|
|
int counter = s_numimages_;
|
|
while (!cache_queue_.empty() && counter--) {
|
|
Cache::ItemPtr ptr = cache_queue_.front();
|
|
cache_set_.erase(ptr);
|
|
cache_queue_.pop_front();
|
|
if (ptr->status() == WaitingToLoad)
|
|
ptr->startLoading();
|
|
}
|
|
if (!cache_queue_.empty())
|
|
startLoader();
|
|
else
|
|
stopLoader();
|
|
}
|
|
|
|
|
|
LoaderQueue::LoaderQueue() : timer(s_millisecs_, Timeout::ONETIME),
|
|
running_(false)
|
|
{
|
|
timer.timeout.connect(bind(&LoaderQueue::loadNext, this));
|
|
}
|
|
|
|
|
|
void LoaderQueue::startLoader()
|
|
{
|
|
LYXERR(Debug::GRAPHICS, "LoaderQueue: waking up");
|
|
running_ = true ;
|
|
timer.setTimeout(s_millisecs_);
|
|
timer.start();
|
|
}
|
|
|
|
|
|
void LoaderQueue::stopLoader()
|
|
{
|
|
timer.stop();
|
|
running_ = false ;
|
|
LYXERR(Debug::GRAPHICS, "LoaderQueue: I'm going to sleep");
|
|
}
|
|
|
|
|
|
bool LoaderQueue::running() const
|
|
{
|
|
return running_ ;
|
|
}
|
|
|
|
|
|
void LoaderQueue::touch(Cache::ItemPtr const & item)
|
|
{
|
|
if (! cache_set_.insert(item).second) {
|
|
list<Cache::ItemPtr>::iterator
|
|
it = cache_queue_.begin();
|
|
list<Cache::ItemPtr>::iterator
|
|
end = cache_queue_.end();
|
|
|
|
it = find(it, end, item);
|
|
if (it != end)
|
|
cache_queue_.erase(it);
|
|
}
|
|
cache_queue_.push_front(item);
|
|
if (!running_)
|
|
startLoader();
|
|
}
|
|
|
|
|
|
|
|
/////////////////////////////////////////////////////////////////////
|
|
//
|
|
// GraphicsLoader
|
|
//
|
|
/////////////////////////////////////////////////////////////////////
|
|
|
|
typedef std::shared_ptr<Image> ImagePtr;
|
|
|
|
class Loader::Impl : public boost::signals2::trackable {
|
|
friend class Loader;
|
|
public:
|
|
///
|
|
Impl(FileName const & doc_file);
|
|
///
|
|
~Impl();
|
|
///
|
|
void resetFile(FileName const &);
|
|
///
|
|
void resetParams(Params const &);
|
|
///
|
|
void createPixmap();
|
|
///
|
|
void startLoading();
|
|
///
|
|
Params const & params() const { return params_; }
|
|
|
|
///
|
|
FileName doc_file_;
|
|
/// The loading status of the image.
|
|
ImageStatus status_;
|
|
/** Must store a copy of the cached item to ensure that it is not
|
|
* erased unexpectedly by the cache itself.
|
|
*/
|
|
Cache::ItemPtr cached_item_;
|
|
/// We modify a local copy of the image once it is loaded.
|
|
ImagePtr image_;
|
|
/// This signal is emitted when the image loading status changes.
|
|
boost::signals2::signal<void()> signal_;
|
|
/// The connection of the signal StatusChanged
|
|
boost::signals2::connection sc_;
|
|
|
|
double displayPixelRatio() const
|
|
{
|
|
return params_.pixel_ratio;
|
|
}
|
|
void setDisplayPixelRatio(double scale)
|
|
{
|
|
params_.pixel_ratio = scale;
|
|
}
|
|
|
|
private:
|
|
///
|
|
void statusChanged();
|
|
///
|
|
void checkedLoading();
|
|
|
|
///
|
|
Params params_;
|
|
};
|
|
|
|
|
|
Loader::Loader(FileName const & doc_file)
|
|
: pimpl_(new Impl(doc_file))
|
|
{}
|
|
|
|
|
|
Loader::Loader(FileName const & doc_file, FileName const & file, bool display)
|
|
: pimpl_(new Impl(doc_file))
|
|
{
|
|
reset(file, display);
|
|
}
|
|
|
|
|
|
Loader::Loader(FileName const & doc_file, FileName const & file, Params const & params)
|
|
: pimpl_(new Impl(doc_file))
|
|
{
|
|
reset(file, params);
|
|
}
|
|
|
|
|
|
Loader::Loader(FileName const & doc_file, Loader const & other)
|
|
: pimpl_(new Impl(doc_file))
|
|
{
|
|
Params const & params = other.pimpl_->params();
|
|
reset(params.filename, params);
|
|
}
|
|
|
|
|
|
Loader::Loader(Loader const & other)
|
|
: pimpl_(new Impl(other.pimpl_->doc_file_))
|
|
{
|
|
Params const & params = other.pimpl_->params();
|
|
reset(params.filename, params);
|
|
}
|
|
|
|
|
|
Loader::~Loader()
|
|
{
|
|
delete pimpl_;
|
|
}
|
|
|
|
|
|
Loader & Loader::operator=(Loader const & other)
|
|
{
|
|
LASSERT(false, /**/);
|
|
if (this != &other) {
|
|
delete pimpl_;
|
|
pimpl_ = new Impl(other.pimpl_->doc_file_);
|
|
Params const & params = other.pimpl_->params();
|
|
reset(params.filename, params);
|
|
}
|
|
return *this;
|
|
}
|
|
|
|
|
|
void Loader::reset(FileName const & file, bool display) const
|
|
{
|
|
Params params;
|
|
params.display = display;
|
|
pimpl_->resetParams(params);
|
|
|
|
pimpl_->resetFile(file);
|
|
pimpl_->createPixmap();
|
|
}
|
|
|
|
|
|
void Loader::reset(FileName const & file, Params const & params) const
|
|
{
|
|
pimpl_->resetParams(params);
|
|
pimpl_->resetFile(file);
|
|
pimpl_->createPixmap();
|
|
}
|
|
|
|
|
|
void Loader::reset(Params const & params) const
|
|
{
|
|
pimpl_->resetParams(params);
|
|
pimpl_->createPixmap();
|
|
}
|
|
|
|
|
|
void Loader::startLoading() const
|
|
{
|
|
if (pimpl_->status_ != WaitingToLoad || !pimpl_->cached_item_)
|
|
return;
|
|
pimpl_->startLoading();
|
|
}
|
|
|
|
|
|
void Loader::reload() const
|
|
{
|
|
pimpl_->cached_item_->startLoading();
|
|
}
|
|
|
|
|
|
void Loader::startMonitoring() const
|
|
{
|
|
if (!pimpl_->cached_item_)
|
|
return;
|
|
|
|
pimpl_->cached_item_->startMonitoring();
|
|
}
|
|
|
|
|
|
bool Loader::monitoring() const
|
|
{
|
|
if (!pimpl_->cached_item_)
|
|
return false;
|
|
|
|
return pimpl_->cached_item_->monitoring();
|
|
}
|
|
|
|
|
|
unsigned long Loader::checksum() const
|
|
{
|
|
if (!pimpl_->cached_item_)
|
|
return 0;
|
|
|
|
return pimpl_->cached_item_->checksum();
|
|
}
|
|
|
|
|
|
FileName const & Loader::filename() const
|
|
{
|
|
static FileName const empty;
|
|
return pimpl_->cached_item_ ?
|
|
pimpl_->cached_item_->filename() : empty;
|
|
}
|
|
|
|
|
|
ImageStatus Loader::status() const
|
|
{
|
|
return pimpl_->status_;
|
|
}
|
|
|
|
|
|
double Loader::displayPixelRatio() const
|
|
{
|
|
return pimpl_->displayPixelRatio();
|
|
}
|
|
|
|
|
|
void Loader::setDisplayPixelRatio(double scale)
|
|
{
|
|
pimpl_->setDisplayPixelRatio(scale);
|
|
}
|
|
|
|
|
|
boost::signals2::connection Loader::connect(slot_type const & slot) const
|
|
{
|
|
return pimpl_->signal_.connect(slot);
|
|
}
|
|
|
|
|
|
Image const * Loader::image() const
|
|
{
|
|
return pimpl_->image_.get();
|
|
}
|
|
|
|
|
|
Loader::Impl::Impl(FileName const & doc_file)
|
|
: doc_file_(doc_file), status_(WaitingToLoad)
|
|
{
|
|
}
|
|
|
|
|
|
Loader::Impl::~Impl()
|
|
{
|
|
resetFile(FileName());
|
|
}
|
|
|
|
|
|
void Loader::Impl::resetFile(FileName const & file)
|
|
{
|
|
FileName const old_file = cached_item_ ?
|
|
cached_item_->filename() : FileName();
|
|
|
|
if (file == old_file)
|
|
return;
|
|
|
|
// If monitoring() the current file, should continue to monitor the
|
|
// new file.
|
|
bool continue_monitoring = false;
|
|
|
|
if (!old_file.empty()) {
|
|
continue_monitoring = cached_item_->monitoring();
|
|
// cached_item_ is going to be reset, so the connected
|
|
// signal needs to be disconnected.
|
|
sc_.disconnect();
|
|
cached_item_.reset();
|
|
if (status_ != Converting) {
|
|
Cache::get().remove(old_file);
|
|
} else {
|
|
//TODO remove cache item when it is not busy any more, see #7163
|
|
}
|
|
}
|
|
|
|
status_ = cached_item_ ? cached_item_->status() : WaitingToLoad;
|
|
image_.reset();
|
|
|
|
if (cached_item_ || file.empty())
|
|
return;
|
|
|
|
Cache & gc = Cache::get();
|
|
if (!gc.inCache(file))
|
|
gc.add(file, doc_file_);
|
|
|
|
// We /must/ make a local copy of this.
|
|
cached_item_ = gc.item(file);
|
|
status_ = cached_item_->status();
|
|
|
|
if (continue_monitoring && !cached_item_->monitoring())
|
|
cached_item_->startMonitoring();
|
|
|
|
sc_ = cached_item_->connect(bind(&Impl::statusChanged, this));
|
|
}
|
|
|
|
|
|
void Loader::Impl::resetParams(Params const & params)
|
|
{
|
|
if (params == params_)
|
|
return;
|
|
|
|
params_ = params;
|
|
status_ = cached_item_ ? cached_item_->status() : WaitingToLoad;
|
|
image_.reset();
|
|
}
|
|
|
|
|
|
void Loader::Impl::statusChanged()
|
|
{
|
|
status_ = cached_item_ ? cached_item_->status() : WaitingToLoad;
|
|
createPixmap();
|
|
signal_();
|
|
}
|
|
|
|
|
|
void Loader::Impl::createPixmap()
|
|
{
|
|
if (!params_.display || status_ != Loaded)
|
|
return;
|
|
|
|
if (!cached_item_) {
|
|
LYXERR(Debug::GRAPHICS, "pixmap not cached yet");
|
|
return;
|
|
}
|
|
|
|
if (!cached_item_->image()) {
|
|
// There must have been a problem reading the file.
|
|
LYXERR(Debug::GRAPHICS, "Graphics file not loaded.");
|
|
return;
|
|
}
|
|
|
|
image_.reset(cached_item_->image()->clone());
|
|
|
|
if (params_.pixel_ratio == 1.0) {
|
|
string filename = cached_item_->filename().absFileName();
|
|
size_t idx = filename.find_last_of('.');
|
|
if (idx != string::npos && idx > 3) {
|
|
if (filename.substr(idx - 3, 3) == "@2x") {
|
|
params_.pixel_ratio = 2.0;
|
|
}
|
|
}
|
|
}
|
|
|
|
bool const success = image_->setPixmap(params_);
|
|
|
|
if (success) {
|
|
status_ = Ready;
|
|
} else {
|
|
image_.reset();
|
|
status_ = ErrorGeneratingPixmap;
|
|
}
|
|
}
|
|
|
|
void Loader::Impl::startLoading()
|
|
{
|
|
if (status_ != WaitingToLoad)
|
|
return;
|
|
|
|
if (cached_item_->tryDisplayFormat()) {
|
|
status_ = Loaded;
|
|
createPixmap();
|
|
return;
|
|
}
|
|
|
|
LoaderQueue::get().touch(cached_item_);
|
|
}
|
|
|
|
|
|
} // namespace graphics
|
|
} // namespace lyx
|