mirror of
https://git.lyx.org/repos/lyx.git
synced 2024-12-22 13:18:28 +00:00
Add a cache for converted image files. This needs to be enabled in the
preferences file with \use_converter_cache true. It is disabled by default, and no GUI support for changing the preferences is yet implemented. * src/insets/insetgraphics.C (InsetGraphics::prepareFile): Use image file cache * src/insets/ExternalSupport.C (updateExternal): Use image file cache * src/exporter.C (Exporter::Export): Do not use image file cache * src/graphics/GraphicsCacheItem.C (CacheItem::Impl::imageConverted): Add the converted file to the image file cache (CacheItem::Impl::convertToDisplayFo): Use image file cache * src/converter.C (Converters::convert): Use image file cache if the caller allowed that * src/converter.h (Converters::convert): Adjust arguments * src/Makefile.am: Add new files * src/support/lyxlib.h (chmod): new function (copy): add mode argument * src/support/copy.C (chmod): new function (copy): implement mode argument * src/support/mkdir.C (lyx::support::mkdir): Add warning if permissions are ignored * src/lyxrc.[Ch]: Add new settings \converter_cache_maxage and \use_converter_cache * src/ConverterCache.[Ch]: New image file cache * src/importer.C (Importer::Import): Do nut use the image file cache * src/lyx_main.C (LyX::init): Initialize the image file cache * src/mover.[Ch] (Mover::do_copy): Add mode argument (SpecialisedMover::do_copy): ditto * configure.ac: Check for chmod * development/cmake/ConfigureChecks.cmake: ditto * development/cmake/config.h.cmake: ditto * development/scons/SConstruct: ditto * development/scons/scons_manifest.py: Add new files git-svn-id: svn://svn.lyx.org/lyx/lyx-devel/trunk@15897 a592a061-630c-0410-9148-cb99ea01b6c8
This commit is contained in:
parent
6ff8d78118
commit
5ed606f9c5
@ -260,7 +260,7 @@ dnl work correctly because of some conflict with stdlib.h with g++ 2.96
|
||||
dnl We aim to remove this eventually, since we should test as much as
|
||||
dnl possible with the compiler which will use the functions (JMarc)
|
||||
AC_LANG_PUSH(C)
|
||||
AC_CHECK_FUNCS(close _close getpid _getpid lstat mkfifo mkstemp mktemp open _open pclose _pclose popen _popen readlink)
|
||||
AC_CHECK_FUNCS(chmod close _close getpid _getpid lstat mkfifo mkstemp mktemp open _open pclose _pclose popen _popen readlink)
|
||||
AC_LANG_POP(C)
|
||||
|
||||
LYX_CHECK_SPELL_ENGINES
|
||||
|
@ -41,6 +41,7 @@ check_include_files(argz.h HAVE_ARGZ_H)
|
||||
|
||||
|
||||
check_function_exists(open HAVE_OPEN)
|
||||
check_function_exists(chmod HAVE_CHMOD)
|
||||
check_function_exists(close HAVE_CLOSE)
|
||||
check_function_exists(popen HAVE_POPEN)
|
||||
check_function_exists(pclose HAVE_PCLOSE)
|
||||
|
@ -33,6 +33,7 @@
|
||||
#cmakedefine HAVE_IOS 1
|
||||
#cmakedefine HAVE_LOCALE 1
|
||||
#cmakedefine HAVE_OPEN 1
|
||||
#cmakedefine HAVE_CHMOD 1
|
||||
#cmakedefine HAVE_CLOSE 1
|
||||
#cmakedefine HAVE_POPEN 1
|
||||
#cmakedefine HAVE_PCLOSE 1
|
||||
|
@ -986,6 +986,7 @@ result = utils.createConfigFile(conf,
|
||||
],
|
||||
functions = [
|
||||
('open', 'HAVE_OPEN', None),
|
||||
('chmod', 'HAVE_CHMOD', None),
|
||||
('close', 'HAVE_CLOSE', None),
|
||||
('popen', 'HAVE_POPEN', None),
|
||||
('pclose', 'HAVE_PCLOSE', None),
|
||||
|
@ -1034,6 +1034,7 @@ src_header_files = Split('''
|
||||
Bullet.h
|
||||
Chktex.h
|
||||
Color.h
|
||||
ConverterCache.h
|
||||
CutAndPaste.h
|
||||
DepTable.h
|
||||
FloatList.h
|
||||
@ -1155,6 +1156,7 @@ src_pre_files = Split('''
|
||||
Bullet.C
|
||||
Chktex.C
|
||||
Color.C
|
||||
ConverterCache.C
|
||||
CutAndPaste.C
|
||||
DepTable.C
|
||||
FloatList.C
|
||||
|
349
src/ConverterCache.C
Normal file
349
src/ConverterCache.C
Normal file
@ -0,0 +1,349 @@
|
||||
/**
|
||||
* \file ConverterCache.C
|
||||
* This file is part of LyX, the document processor.
|
||||
* Licence details can be found in the file COPYING.
|
||||
*
|
||||
* \author Baruch Even
|
||||
* \author Angus Leeming
|
||||
* \author Georg Baum
|
||||
*
|
||||
* Full author contact details are available in file CREDITS.
|
||||
*/
|
||||
|
||||
#include <config.h>
|
||||
|
||||
#include "ConverterCache.h"
|
||||
|
||||
#include "debug.h"
|
||||
#include "lyxrc.h"
|
||||
#include "mover.h"
|
||||
|
||||
#include "support/filetools.h"
|
||||
#include "support/lyxlib.h"
|
||||
#include "support/lyxtime.h"
|
||||
#include "support/package.h"
|
||||
|
||||
#include <boost/crc.hpp>
|
||||
#include <boost/filesystem/operations.hpp>
|
||||
|
||||
#include <fstream>
|
||||
#include <iomanip>
|
||||
#include <map>
|
||||
#include <sstream>
|
||||
|
||||
using lyx::support::absolutePath;
|
||||
using lyx::support::addName;
|
||||
|
||||
using std::string;
|
||||
|
||||
namespace fs = boost::filesystem;
|
||||
|
||||
namespace lyx {
|
||||
|
||||
namespace {
|
||||
|
||||
unsigned long do_crc(string const & s)
|
||||
{
|
||||
boost::crc_32_type crc;
|
||||
crc = std::for_each(s.begin(), s.end(), crc);
|
||||
return crc.checksum();
|
||||
}
|
||||
|
||||
|
||||
static string cache_dir;
|
||||
|
||||
|
||||
class CacheItem {
|
||||
public:
|
||||
CacheItem() {}
|
||||
CacheItem(string const & orig_from, string const & to_format,
|
||||
time_t t, unsigned long c)
|
||||
: timestamp(t), checksum(c)
|
||||
{
|
||||
BOOST_ASSERT(absolutePath(orig_from));
|
||||
std::ostringstream os;
|
||||
os << std::setw(10) << std::setfill('0') << do_crc(orig_from)
|
||||
<< '-' << to_format;
|
||||
cache_name = addName(cache_dir, os.str());
|
||||
lyxerr[Debug::FILES] << "Add file cache item " << orig_from
|
||||
<< ' ' << to_format << ' ' << cache_name
|
||||
<< ' ' << timestamp << ' ' << checksum
|
||||
<< '.' << std::endl;
|
||||
}
|
||||
~CacheItem() {}
|
||||
string cache_name;
|
||||
time_t timestamp;
|
||||
unsigned long checksum;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
|
||||
/** The cache contains one item per orig file and target format, so use a
|
||||
* nested map to find the cache item quickly by filename and format.
|
||||
*/
|
||||
typedef std::map<string, CacheItem> FormatCacheType;
|
||||
typedef std::map<string, FormatCacheType> CacheType;
|
||||
|
||||
|
||||
class ConverterCache::Impl {
|
||||
public:
|
||||
///
|
||||
void readIndex();
|
||||
///
|
||||
void writeIndex();
|
||||
///
|
||||
CacheItem * find(string const & from, string const & format);
|
||||
CacheType cache;
|
||||
};
|
||||
|
||||
|
||||
void ConverterCache::Impl::readIndex()
|
||||
{
|
||||
time_t const now = current_time();
|
||||
string const index = addName(cache_dir, "index");
|
||||
std::ifstream is(index.c_str());
|
||||
while (is.good()) {
|
||||
string orig_from;
|
||||
string to_format;
|
||||
time_t timestamp;
|
||||
unsigned long checksum;
|
||||
if (!(is >> orig_from >> to_format >> timestamp >> checksum))
|
||||
return;
|
||||
CacheItem item(orig_from, to_format, timestamp, checksum);
|
||||
|
||||
// Don't cache files that do not exist anymore
|
||||
if (!fs::exists(orig_from)) {
|
||||
lyxerr[Debug::FILES] << "Not caching file `"
|
||||
<< orig_from << "' (does not exist anymore)."
|
||||
<< std::endl;
|
||||
support::unlink(item.cache_name);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Delete the cached file if it is too old
|
||||
if (difftime(now, fs::last_write_time(item.cache_name)) >
|
||||
lyxrc.converter_cache_maxage) {
|
||||
lyxerr[Debug::FILES] << "Not caching file `"
|
||||
<< orig_from << "' (too old)." << std::endl;
|
||||
support::unlink(item.cache_name);
|
||||
continue;
|
||||
}
|
||||
|
||||
cache[orig_from][to_format] = item;
|
||||
}
|
||||
is.close();
|
||||
}
|
||||
|
||||
|
||||
void ConverterCache::Impl::writeIndex()
|
||||
{
|
||||
string const index = addName(cache_dir, "index");
|
||||
std::ofstream os(index.c_str());
|
||||
os.close();
|
||||
if (!lyx::support::chmod(index.c_str(), 0600))
|
||||
return;
|
||||
os.open(index.c_str());
|
||||
CacheType::iterator it1 = cache.begin();
|
||||
CacheType::iterator const end1 = cache.end();
|
||||
for (; it1 != end1; ++it1) {
|
||||
FormatCacheType::iterator it2 = it1->second.begin();
|
||||
FormatCacheType::iterator const end2 = it1->second.end();
|
||||
for (; it2 != end2; ++it2)
|
||||
os << it1->first << ' ' << it2->first << ' '
|
||||
<< it2->second.timestamp << ' '
|
||||
<< it2->second.checksum << '\n';
|
||||
}
|
||||
os.close();
|
||||
}
|
||||
|
||||
|
||||
CacheItem * ConverterCache::Impl::find(string const & from,
|
||||
string const & format)
|
||||
{
|
||||
if (!lyxrc.use_converter_cache)
|
||||
return 0;
|
||||
CacheType::iterator const it1 = cache.find(from);
|
||||
if (it1 == cache.end())
|
||||
return 0;
|
||||
FormatCacheType::iterator const it2 = it1->second.find(format);
|
||||
if (it2 == it1->second.end())
|
||||
return 0;
|
||||
return &(it2->second);
|
||||
}
|
||||
|
||||
|
||||
ConverterCache & ConverterCache::get()
|
||||
{
|
||||
// Now return the cache
|
||||
static ConverterCache singleton;
|
||||
return singleton;
|
||||
}
|
||||
|
||||
|
||||
void ConverterCache::init()
|
||||
{
|
||||
if (!lyxrc.use_converter_cache)
|
||||
return;
|
||||
// We do this here and not in the constructor because package() gets
|
||||
// initialized after all static variables.
|
||||
cache_dir = addName(support::package().user_support(), "cache");
|
||||
if (!fs::exists(cache_dir))
|
||||
if (support::mkdir(cache_dir, 0700) != 0) {
|
||||
lyxerr << "Could not create cache directory `"
|
||||
<< cache_dir << "'." << std::endl;
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
get().pimpl_->readIndex();
|
||||
}
|
||||
|
||||
|
||||
ConverterCache::ConverterCache()
|
||||
: pimpl_(new Impl)
|
||||
{}
|
||||
|
||||
|
||||
ConverterCache::~ConverterCache()
|
||||
{
|
||||
if (!lyxrc.use_converter_cache)
|
||||
return;
|
||||
pimpl_->writeIndex();
|
||||
}
|
||||
|
||||
|
||||
void ConverterCache::add(string const & orig_from, string const & to_format,
|
||||
string const & converted_file) const
|
||||
{
|
||||
if (!lyxrc.use_converter_cache)
|
||||
return;
|
||||
lyxerr[Debug::FILES] << BOOST_CURRENT_FUNCTION << ' ' << orig_from
|
||||
<< ' ' << to_format << ' ' << converted_file
|
||||
<< std::endl;
|
||||
BOOST_ASSERT(absolutePath(orig_from));
|
||||
BOOST_ASSERT(absolutePath(converted_file));
|
||||
|
||||
// Is the file in the cache already?
|
||||
CacheItem * item = pimpl_->find(orig_from, to_format);
|
||||
|
||||
time_t const timestamp = fs::last_write_time(orig_from);
|
||||
Mover const & mover = movers(to_format);
|
||||
if (item) {
|
||||
lyxerr[Debug::FILES] << "ConverterCache::add(" << orig_from << "):\n"
|
||||
"The file is already in the cache."
|
||||
<< std::endl;
|
||||
// First test for timestamp
|
||||
if (timestamp == item->timestamp) {
|
||||
lyxerr[Debug::FILES] << "Same timestamp."
|
||||
<< std::endl;
|
||||
return;
|
||||
} else {
|
||||
// Maybe the contents is still the same?
|
||||
item->timestamp = timestamp;
|
||||
unsigned long const checksum = support::sum(orig_from);
|
||||
if (checksum == item->checksum) {
|
||||
lyxerr[Debug::FILES] << "Same checksum."
|
||||
<< std::endl;
|
||||
return;
|
||||
}
|
||||
item->checksum = checksum;
|
||||
}
|
||||
if (!mover.copy(converted_file, item->cache_name, 0600))
|
||||
lyxerr[Debug::FILES] << "ConverterCache::add("
|
||||
<< orig_from << "):\n"
|
||||
"Could not copy file."
|
||||
<< std::endl;
|
||||
} else {
|
||||
CacheItem new_item = CacheItem(orig_from, to_format, timestamp,
|
||||
support::sum(orig_from));
|
||||
if (mover.copy(converted_file, new_item.cache_name, 0600))
|
||||
pimpl_->cache[orig_from][to_format] = new_item;
|
||||
else
|
||||
lyxerr[Debug::FILES] << "ConverterCache::add("
|
||||
<< orig_from << "):\n"
|
||||
"Could not copy file."
|
||||
<< std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void ConverterCache::remove(string const & orig_from,
|
||||
string const & to_format) const
|
||||
{
|
||||
if (!lyxrc.use_converter_cache)
|
||||
return;
|
||||
lyxerr[Debug::FILES] << BOOST_CURRENT_FUNCTION << ' ' << orig_from
|
||||
<< ' ' << to_format << std::endl;
|
||||
BOOST_ASSERT(absolutePath(orig_from));
|
||||
|
||||
CacheType::iterator const it1 = pimpl_->cache.find(orig_from);
|
||||
if (it1 == pimpl_->cache.end())
|
||||
return;
|
||||
FormatCacheType::iterator const it2 = it1->second.find(to_format);
|
||||
if (it2 == it1->second.end())
|
||||
return;
|
||||
|
||||
it1->second.erase(it2);
|
||||
if (it1->second.empty())
|
||||
pimpl_->cache.erase(it1);
|
||||
}
|
||||
|
||||
|
||||
bool ConverterCache::inCache(string const & orig_from,
|
||||
string const & to_format) const
|
||||
{
|
||||
if (!lyxrc.use_converter_cache)
|
||||
return false;
|
||||
lyxerr[Debug::FILES] << BOOST_CURRENT_FUNCTION << ' ' << orig_from
|
||||
<< ' ' << to_format << std::endl;
|
||||
BOOST_ASSERT(absolutePath(orig_from));
|
||||
|
||||
CacheItem * const item = pimpl_->find(orig_from, to_format);
|
||||
if (!item) {
|
||||
lyxerr[Debug::FILES] << "not in cache." << std::endl;
|
||||
return false;
|
||||
}
|
||||
time_t const timestamp = fs::last_write_time(orig_from);
|
||||
if (item->timestamp == timestamp) {
|
||||
lyxerr[Debug::FILES] << "identical timestamp." << std::endl;
|
||||
return true;
|
||||
}
|
||||
if (item->checksum == support::sum(orig_from)) {
|
||||
item->timestamp = timestamp;
|
||||
lyxerr[Debug::FILES] << "identical checksum." << std::endl;
|
||||
return true;
|
||||
}
|
||||
lyxerr[Debug::FILES] << "in cache, but too old." << std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
string const ConverterCache::cacheName(string const & orig_from,
|
||||
string const & to_format) const
|
||||
{
|
||||
lyxerr[Debug::FILES] << BOOST_CURRENT_FUNCTION << ' ' << orig_from
|
||||
<< ' ' << to_format << std::endl;
|
||||
BOOST_ASSERT(absolutePath(orig_from));
|
||||
|
||||
CacheItem * const item = pimpl_->find(orig_from, to_format);
|
||||
BOOST_ASSERT(item);
|
||||
return item->cache_name;
|
||||
}
|
||||
|
||||
|
||||
bool ConverterCache::copy(string const & orig_from, string const & to_format,
|
||||
string const & dest) const
|
||||
{
|
||||
if (!lyxrc.use_converter_cache)
|
||||
return false;
|
||||
lyxerr[Debug::FILES] << BOOST_CURRENT_FUNCTION << ' ' << orig_from
|
||||
<< ' ' << to_format << ' ' << dest << std::endl;
|
||||
BOOST_ASSERT(absolutePath(orig_from));
|
||||
BOOST_ASSERT(absolutePath(dest));
|
||||
|
||||
CacheItem * const item = pimpl_->find(orig_from, to_format);
|
||||
BOOST_ASSERT(item);
|
||||
Mover const & mover = movers(to_format);
|
||||
return mover.copy(item->cache_name, dest);
|
||||
}
|
||||
|
||||
} // namespace lyx
|
101
src/ConverterCache.h
Normal file
101
src/ConverterCache.h
Normal file
@ -0,0 +1,101 @@
|
||||
// -*- C++ -*-
|
||||
/**
|
||||
* \file ConverterCache.h
|
||||
* This file is part of LyX, the document processor.
|
||||
* Licence details can be found in the file COPYING.
|
||||
*
|
||||
* \author Baruch Even
|
||||
* \author Angus Leeming
|
||||
* \author Georg Baum
|
||||
*
|
||||
* Full author contact details are available in file CREDITS.
|
||||
*
|
||||
* lyx::ConverterCache is the manager of the file cache.
|
||||
* It is responsible for creating the lyx::ConverterCacheItem's
|
||||
* and maintaining them.
|
||||
*
|
||||
* lyx::ConverterCache is a singleton class. It is possible to have
|
||||
* only one instance of it at any moment.
|
||||
*/
|
||||
|
||||
#ifndef CONVERTERCACHE_H
|
||||
#define CONVERTERCACHE_H
|
||||
|
||||
#include <boost/utility.hpp>
|
||||
#include <boost/scoped_ptr.hpp>
|
||||
|
||||
#include <string>
|
||||
|
||||
|
||||
namespace lyx {
|
||||
|
||||
/**
|
||||
* Cache for converted files. The cache works as follows:
|
||||
*
|
||||
* The key for a cache item consists of the absolute name of the original
|
||||
* file and the format name of the target format. The original file in the
|
||||
* user directory is named \c orig_from in the code, the format name is named
|
||||
* \c to_format. Example:
|
||||
* \c orig_from = "/home/me/myfigure.fig"
|
||||
* \c to_format = "eps"
|
||||
* A cache item is considered up to date (inCache() returns \c true) if
|
||||
* - The cache contains an item with key (\c orig_to, \c to_format)
|
||||
* - The stored timestamp of the item is identical with the actual timestamp
|
||||
* of \c orig_from, or, if that is not the case, the stored checksum is
|
||||
* identical with the actual checksum of \c orig_from.
|
||||
* Otherwise the item is not considered up to date, and add() will refresh it.
|
||||
*
|
||||
* There is no cache maintenance yet (max size, max age etc.)
|
||||
*/
|
||||
class ConverterCache : boost::noncopyable {
|
||||
public:
|
||||
|
||||
/// This is a singleton class. Get the instance.
|
||||
static ConverterCache & get();
|
||||
|
||||
/// Init the cache. This must be done after package initialization.
|
||||
static void init();
|
||||
|
||||
/**
|
||||
* Add \c converted_file (\c orig_from converted to \c to_format) to
|
||||
* the cache if it is not already in or not up to date.
|
||||
*/
|
||||
void add(std::string const & orig_from, std::string const & to_format,
|
||||
std::string const & converted_file) const;
|
||||
|
||||
/// Remove a file from the cache.
|
||||
void remove(std::string const & orig_from,
|
||||
std::string const & to_format) const;
|
||||
|
||||
/**
|
||||
* Returns \c true if \c orig_from converted to \c to_format is in
|
||||
* the cache and up to date.
|
||||
*/
|
||||
bool inCache(std::string const & orig_from,
|
||||
std::string const & to_format) const;
|
||||
|
||||
/// Get the name of the cached file
|
||||
std::string const cacheName(std::string const & orig_from,
|
||||
std::string const & to_format) const;
|
||||
|
||||
/// Copy the file from the cache to \p dest
|
||||
bool copy(std::string const & orig_from, std::string const & to_format,
|
||||
std::string const & dest) const;
|
||||
|
||||
private:
|
||||
/** Make the c-tor, d-tor private so we can control how many objects
|
||||
* are instantiated.
|
||||
*/
|
||||
ConverterCache();
|
||||
///
|
||||
~ConverterCache();
|
||||
|
||||
/// Use the Pimpl idiom to hide the internals.
|
||||
class Impl;
|
||||
/// The pointer never changes although *pimpl_'s contents may.
|
||||
boost::scoped_ptr<Impl> const pimpl_;
|
||||
};
|
||||
|
||||
} // namespace lyx
|
||||
|
||||
#endif
|
@ -72,6 +72,8 @@ lyx_SOURCES = \
|
||||
Chktex.h \
|
||||
Color.C \
|
||||
Color.h \
|
||||
ConverterCache.C \
|
||||
ConverterCache.h \
|
||||
CutAndPaste.C \
|
||||
CutAndPaste.h \
|
||||
DepTable.C \
|
||||
|
@ -12,6 +12,7 @@
|
||||
|
||||
#include "converter.h"
|
||||
|
||||
#include "ConverterCache.h"
|
||||
#include "buffer.h"
|
||||
#include "buffer_funcs.h"
|
||||
#include "bufferparams.h"
|
||||
@ -33,6 +34,7 @@
|
||||
|
||||
namespace lyx {
|
||||
|
||||
using support::absolutePath;
|
||||
using support::addName;
|
||||
using support::bformat;
|
||||
using support::changeExtension;
|
||||
@ -285,24 +287,31 @@ OutputParams::FLAVOR Converters::getFlavor(Graph::EdgePath const & path)
|
||||
|
||||
|
||||
bool Converters::convert(Buffer const * buffer,
|
||||
string const & from_file, string const & to_file_base,
|
||||
string const & from_format, string const & to_format,
|
||||
string & to_file, ErrorList & errorList, bool try_default)
|
||||
string const & from_file, string const & to_file,
|
||||
string const & orig_from,
|
||||
string const & from_format, string const & to_format,
|
||||
ErrorList & errorList, int conversionflags)
|
||||
{
|
||||
string const to_ext = formats.extension(to_format);
|
||||
to_file = changeExtension(to_file_base, to_ext);
|
||||
BOOST_ASSERT(absolutePath(from_file));
|
||||
BOOST_ASSERT(absolutePath(to_file));
|
||||
BOOST_ASSERT(absolutePath(orig_from));
|
||||
|
||||
if (from_format == to_format)
|
||||
return move(from_format, from_file, to_file, false);
|
||||
|
||||
if ((conversionflags & try_cache) &&
|
||||
ConverterCache::get().inCache(orig_from, to_format))
|
||||
return ConverterCache::get().copy(orig_from, to_format, to_file);
|
||||
|
||||
Graph::EdgePath edgepath = getPath(from_format, to_format);
|
||||
if (edgepath.empty()) {
|
||||
if (try_default) {
|
||||
if (conversionflags & try_default) {
|
||||
// if no special converter defined, then we take the
|
||||
// default one from ImageMagic.
|
||||
string const from_ext = from_format.empty() ?
|
||||
getExtension(from_file) :
|
||||
formats.extension(from_format);
|
||||
string const to_ext = formats.extension(to_format);
|
||||
string const command =
|
||||
support::os::python() + ' ' +
|
||||
quoteName(libFileSearch("scripts", "convertDefault.py")) +
|
||||
@ -317,6 +326,9 @@ bool Converters::convert(Buffer const * buffer,
|
||||
Systemcall one;
|
||||
one.startscript(Systemcall::Wait, command);
|
||||
if (isFileReadable(to_file)) {
|
||||
if (conversionflags & try_cache)
|
||||
ConverterCache::get().add(orig_from,
|
||||
to_format, to_file);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@ -466,9 +478,8 @@ bool Converters::convert(Buffer const * buffer,
|
||||
return true;
|
||||
|
||||
if (!conv.result_dir.empty()) {
|
||||
to_file = addName(subst(conv.result_dir, token_base, to_base),
|
||||
subst(conv.result_file,
|
||||
token_base, onlyFilename(to_base)));
|
||||
// The converter has put the file(s) in a directory.
|
||||
// In this case we ignore the given to_file.
|
||||
if (from_base != to_base) {
|
||||
string const from = subst(conv.result_dir,
|
||||
token_base, from_base);
|
||||
@ -477,14 +488,17 @@ bool Converters::convert(Buffer const * buffer,
|
||||
Mover const & mover = movers(conv.from);
|
||||
if (!mover.rename(from, to)) {
|
||||
Alert::error(_("Cannot convert file"),
|
||||
bformat(_("Could not move a temporary file from %1$s to %2$s."),
|
||||
bformat(_("Could not move a temporary directory from %1$s to %2$s."),
|
||||
from_ascii(from), from_ascii(to)));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
} else
|
||||
} else {
|
||||
if (conversionflags & try_cache)
|
||||
ConverterCache::get().add(orig_from, to_format, outfile);
|
||||
return move(conv.to, outfile, to_file, conv.latex);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -527,17 +541,6 @@ bool Converters::move(string const & fmt,
|
||||
}
|
||||
|
||||
|
||||
bool Converters::convert(Buffer const * buffer,
|
||||
string const & from_file, string const & to_file_base,
|
||||
string const & from_format, string const & to_format,
|
||||
ErrorList & errorList, bool try_default)
|
||||
{
|
||||
string to_file;
|
||||
return convert(buffer, from_file, to_file_base, from_format, to_format,
|
||||
to_file, errorList, try_default);
|
||||
}
|
||||
|
||||
|
||||
bool Converters::formatIsUsed(string const & format)
|
||||
{
|
||||
ConverterList::const_iterator cit = converterlist_.begin();
|
||||
|
@ -107,17 +107,21 @@ public:
|
||||
Graph::EdgePath const getPath(std::string const & from, std::string const & to);
|
||||
///
|
||||
OutputParams::FLAVOR getFlavor(Graph::EdgePath const & path);
|
||||
/// Flags for converting files
|
||||
enum ConversionFlags {
|
||||
/// No special flags
|
||||
none = 0,
|
||||
/// Use the default converter if no converter is defined
|
||||
try_default = 1 << 0,
|
||||
/// Get the converted file from cache if possible
|
||||
try_cache = 1 << 1
|
||||
};
|
||||
///
|
||||
bool convert(Buffer const * buffer,
|
||||
std::string const & from_file, std::string const & to_file_base,
|
||||
std::string const & from_format, std::string const & to_format,
|
||||
std::string & to_file, ErrorList & errorList,
|
||||
bool try_default = false);
|
||||
///
|
||||
bool convert(Buffer const * buffer,
|
||||
std::string const & from_file, std::string const & to_file_base,
|
||||
std::string const & from_format, std::string const & to_format,
|
||||
ErrorList & errorList, bool try_default = false);
|
||||
std::string const & from_file, std::string const & to_file,
|
||||
std::string const & orig_from,
|
||||
std::string const & from_format, std::string const & to_format,
|
||||
ErrorList & errorList, int conversionflags = none);
|
||||
///
|
||||
void update(Formats const & formats);
|
||||
///
|
||||
|
@ -217,18 +217,20 @@ bool Exporter::Export(Buffer * buffer, string const & format,
|
||||
}
|
||||
|
||||
string const error_type = (format == "program")? "Build" : bufferFormat(*buffer);
|
||||
bool const success = converters.convert(buffer, filename, filename,
|
||||
backend_format, format, result_file,
|
||||
string const ext = formats.extension(format);
|
||||
string const tmp_result_file = changeExtension(filename, ext);
|
||||
bool const success = converters.convert(buffer, filename,
|
||||
tmp_result_file, buffer->fileName(), backend_format, format,
|
||||
buffer->errorList(error_type));
|
||||
// Emit the signal to show the error list.
|
||||
buffer->errors(error_type);
|
||||
if (!success)
|
||||
return false;
|
||||
|
||||
if (!put_in_tempdir) {
|
||||
string const tmp_result_file = result_file;
|
||||
result_file = changeExtension(buffer->fileName(),
|
||||
formats.extension(format));
|
||||
if (put_in_tempdir)
|
||||
result_file = tmp_result_file;
|
||||
else {
|
||||
result_file = changeExtension(buffer->fileName(), ext);
|
||||
// We need to copy referenced files (e. g. included graphics
|
||||
// if format == "dvi") to the result dir.
|
||||
vector<ExportedFile> const files =
|
||||
|
@ -16,6 +16,7 @@
|
||||
#include "GraphicsConverter.h"
|
||||
#include "GraphicsImage.h"
|
||||
|
||||
#include "ConverterCache.h"
|
||||
#include "debug.h"
|
||||
#include "format.h"
|
||||
|
||||
@ -28,7 +29,6 @@
|
||||
|
||||
namespace lyx {
|
||||
|
||||
using support::changeExtension;
|
||||
using support::FileMonitor;
|
||||
using support::isFileReadable;
|
||||
using support::makeDisplayPath;
|
||||
@ -108,6 +108,8 @@ public:
|
||||
bool zipped_;
|
||||
/// If so, store the uncompressed file in this temporary file.
|
||||
string unzipped_filename_;
|
||||
/// The target format
|
||||
string to_;
|
||||
/// What file are we trying to load?
|
||||
string file_to_load_;
|
||||
/** Should we delete the file after loading? True if the file is
|
||||
@ -228,6 +230,7 @@ void CacheItem::Impl::reset()
|
||||
unlink(file_to_load_);
|
||||
remove_loaded_file_ = false;
|
||||
file_to_load_.erase();
|
||||
to_.erase();
|
||||
|
||||
if (image_.get())
|
||||
image_.reset();
|
||||
@ -278,6 +281,9 @@ void CacheItem::Impl::imageConverted(bool success)
|
||||
return;
|
||||
}
|
||||
|
||||
// Add the converted file to the file cache
|
||||
ConverterCache::get().add(filename_, to_, file_to_load_);
|
||||
|
||||
loadImage();
|
||||
}
|
||||
|
||||
@ -403,9 +409,9 @@ void CacheItem::Impl::convertToDisplayFormat()
|
||||
}
|
||||
lyxerr[Debug::GRAPHICS]
|
||||
<< "\n\tThe file contains " << from << " format data." << endl;
|
||||
string const to = findTargetFormat(from);
|
||||
to_ = findTargetFormat(from);
|
||||
|
||||
if (from == to) {
|
||||
if (from == to_) {
|
||||
// No conversion needed!
|
||||
lyxerr[Debug::GRAPHICS] << "\tNo conversion needed (from == to)!" << endl;
|
||||
file_to_load_ = filename;
|
||||
@ -413,7 +419,15 @@ void CacheItem::Impl::convertToDisplayFormat()
|
||||
return;
|
||||
}
|
||||
|
||||
lyxerr[Debug::GRAPHICS] << "\tConverting it to " << to << " format." << endl;
|
||||
if (ConverterCache::get().inCache(filename, to_)) {
|
||||
lyxerr[Debug::GRAPHICS] << "\tNo conversion needed (file in file cache)!"
|
||||
<< endl;
|
||||
file_to_load_ = ConverterCache::get().cacheName(filename, to_);
|
||||
loadImage();
|
||||
return;
|
||||
}
|
||||
|
||||
lyxerr[Debug::GRAPHICS] << "\tConverting it to " << to_ << " format." << endl;
|
||||
|
||||
// Add some stuff to create a uniquely named temporary file.
|
||||
// This file is deleted in loadImage after it is loaded into memory.
|
||||
@ -427,7 +441,7 @@ void CacheItem::Impl::convertToDisplayFormat()
|
||||
// Connect a signal to this->imageConverted and pass this signal to
|
||||
// the graphics converter so that we can load the modified file
|
||||
// on completion of the conversion process.
|
||||
converter_.reset(new Converter(filename, to_file_base, from, to));
|
||||
converter_.reset(new Converter(filename, to_file_base, from, to_));
|
||||
converter_->connect(boost::bind(&Impl::imageConverted, this, _1));
|
||||
converter_->startConversion();
|
||||
}
|
||||
|
@ -53,8 +53,11 @@ bool Importer::Import(LyXView * lv, string const & filename,
|
||||
for (vector<string>::const_iterator it = loaders.begin();
|
||||
it != loaders.end(); ++it) {
|
||||
if (converters.isReachable(format, *it)) {
|
||||
if (!converters.convert(0, filename, filename,
|
||||
format, *it, errorList))
|
||||
string const tofile =
|
||||
changeExtension(filename,
|
||||
formats.extension(*it));
|
||||
if (!converters.convert(0, filename, tofile,
|
||||
filename, format, *it, errorList))
|
||||
return false;
|
||||
loader_format = *it;
|
||||
break;
|
||||
|
@ -305,14 +305,13 @@ void updateExternal(InsetExternalParams const & params,
|
||||
// Yes if to_file does not exist or if from_file is newer than to_file
|
||||
if (support::compare_timestamps(temp_file, abs_to_file) < 0)
|
||||
return; // SUCCESS
|
||||
string const to_file_base =
|
||||
support::changeExtension(to_file, string());
|
||||
|
||||
// FIXME (Abdel 12/08/06): Is there a need to show these errors?
|
||||
ErrorList el;
|
||||
/* bool const success = */
|
||||
converters.convert(&buffer, temp_file, to_file_base,
|
||||
from_format, to_format, el, true);
|
||||
converters.convert(&buffer, temp_file, abs_to_file,
|
||||
abs_from_file, from_format, to_format, el,
|
||||
Converters::try_default | Converters::try_cache);
|
||||
// return success
|
||||
}
|
||||
|
||||
|
@ -722,7 +722,9 @@ string const InsetGraphics::prepareFile(Buffer const & buf,
|
||||
|
||||
// FIXME (Abdel 12/08/06): Is there a need to show these errors?
|
||||
ErrorList el;
|
||||
if (converters.convert(&buf, temp_file, temp_file, from, to, el, true)) {
|
||||
if (converters.convert(&buf, temp_file, to_file, orig_file,
|
||||
from, to, el,
|
||||
Converters::try_default | Converters::try_cache)) {
|
||||
runparams.exportdata->addExternalFile(tex_format,
|
||||
to_file, output_to_file);
|
||||
runparams.exportdata->addExternalFile("dvi",
|
||||
|
@ -17,6 +17,7 @@
|
||||
|
||||
#include "lyx_main.h"
|
||||
|
||||
#include "ConverterCache.h"
|
||||
#include "buffer.h"
|
||||
#include "buffer_funcs.h"
|
||||
#include "bufferlist.h"
|
||||
@ -796,6 +797,11 @@ bool LyX::init()
|
||||
|
||||
lyxerr[Debug::INIT] << "Reading session information '.lyx/session'..." << endl;
|
||||
pimpl_->session_.reset(new Session(lyxrc.num_lastfiles));
|
||||
|
||||
// This must happen after package initialization and after lyxrc is
|
||||
// read, therefore it can't be done by a static object.
|
||||
ConverterCache::init();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
29
src/lyxrc.C
29
src/lyxrc.C
@ -79,6 +79,7 @@ keyword_item lyxrcTags[] = {
|
||||
{ "\\check_lastfiles", LyXRC::RC_CHECKLASTFILES },
|
||||
{ "\\chktex_command", LyXRC::RC_CHKTEX_COMMAND },
|
||||
{ "\\converter", LyXRC::RC_CONVERTER },
|
||||
{ "\\converter_cache_maxage", LyXRC::RC_CONVERTER_CACHE_MAXAGE },
|
||||
{ "\\copier", LyXRC::RC_COPIER },
|
||||
{ "\\cursor_follows_scrollbar", LyXRC::RC_CURSOR_FOLLOWS_SCROLLBAR },
|
||||
{ "\\custom_export_command", LyXRC::RC_CUSTOM_EXPORT_COMMAND },
|
||||
@ -167,6 +168,7 @@ keyword_item lyxrcTags[] = {
|
||||
{ "\\tex_expects_windows_paths", LyXRC::RC_TEX_EXPECTS_WINDOWS_PATHS },
|
||||
{ "\\ui_file", LyXRC::RC_UIFILE },
|
||||
{ "\\use_alt_language", LyXRC::RC_USE_ALT_LANG },
|
||||
{ "\\use_converter_cache", LyXRC::RC_USE_CONVERTER_CACHE },
|
||||
{ "\\use_escape_chars", LyXRC::RC_USE_ESC_CHARS },
|
||||
{ "\\use_input_encoding", LyXRC::RC_USE_INP_ENC },
|
||||
{ "\\use_lastfilepos", LyXRC::RC_USELASTFILEPOS },
|
||||
@ -289,6 +291,8 @@ void LyXRC::setDefaults() {
|
||||
preview = PREVIEW_OFF;
|
||||
preview_hashed_labels = false;
|
||||
preview_scale_factor = "0.9";
|
||||
use_converter_cache = false;
|
||||
converter_cache_maxage = 6 * 30 * 24 * 3600; // 6 months
|
||||
|
||||
user_name = support::user_name();
|
||||
|
||||
@ -1187,6 +1191,17 @@ int LyXRC::read(LyXLex & lexrc)
|
||||
path_prefix = lexrc.getString();
|
||||
break;
|
||||
|
||||
case RC_USE_CONVERTER_CACHE:
|
||||
if (lexrc.next())
|
||||
use_converter_cache = lexrc.getBool();
|
||||
break;
|
||||
|
||||
case RC_CONVERTER_CACHE_MAXAGE:
|
||||
if (lexrc.next())
|
||||
converter_cache_maxage =
|
||||
convert<unsigned int>(lexrc.getString());
|
||||
break;
|
||||
|
||||
case RC_LAST: break; // this is just a dummy
|
||||
}
|
||||
}
|
||||
@ -1463,6 +1478,20 @@ void LyXRC::write(ostream & os, bool ignore_system_lyxrc) const
|
||||
<< preview_scale_factor << '\n';
|
||||
}
|
||||
|
||||
case RC_USE_CONVERTER_CACHE:
|
||||
if (ignore_system_lyxrc ||
|
||||
use_converter_cache != system_lyxrc.use_converter_cache) {
|
||||
os << "\\use_converter_cache "
|
||||
<< convert<string>(use_converter_cache) << '\n';
|
||||
}
|
||||
|
||||
case RC_CONVERTER_CACHE_MAXAGE:
|
||||
if (ignore_system_lyxrc ||
|
||||
converter_cache_maxage != system_lyxrc.converter_cache_maxage) {
|
||||
os << "\\converter_cache_maxage"
|
||||
<< converter_cache_maxage << '\n';
|
||||
}
|
||||
|
||||
os << "\n#\n"
|
||||
<< "# SCREEN & FONTS SECTION ############################\n"
|
||||
<< "#\n\n";
|
||||
|
@ -50,6 +50,7 @@ public:
|
||||
RC_CHECKLASTFILES,
|
||||
RC_CHKTEX_COMMAND,
|
||||
RC_CONVERTER,
|
||||
RC_CONVERTER_CACHE_MAXAGE,
|
||||
RC_COPIER,
|
||||
RC_CURSOR_FOLLOWS_SCROLLBAR,
|
||||
RC_CUSTOM_EXPORT_COMMAND,
|
||||
@ -136,6 +137,7 @@ public:
|
||||
RC_USER_NAME,
|
||||
RC_USETEMPDIR,
|
||||
RC_USE_ALT_LANG,
|
||||
RC_USE_CONVERTER_CACHE,
|
||||
RC_USE_ESC_CHARS,
|
||||
RC_USE_INP_ENC,
|
||||
RC_USE_PERS_DICT,
|
||||
@ -396,6 +398,10 @@ public:
|
||||
* The string is input, stored and output in native format.
|
||||
*/
|
||||
std::string path_prefix;
|
||||
/// Use the cache for file converters?
|
||||
bool use_converter_cache;
|
||||
/// The maximum age of cache files in seconds
|
||||
unsigned int converter_cache_maxage;
|
||||
};
|
||||
|
||||
|
||||
|
21
src/mover.C
21
src/mover.C
@ -17,11 +17,13 @@
|
||||
#include "support/lyxlib.h"
|
||||
#include "support/systemcall.h"
|
||||
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
|
||||
|
||||
namespace lyx {
|
||||
|
||||
using std::ios;
|
||||
using std::string;
|
||||
|
||||
Movers movers;
|
||||
@ -29,9 +31,9 @@ Movers system_movers;
|
||||
|
||||
|
||||
bool Mover::do_copy(string const & from, string const & to,
|
||||
string const &) const
|
||||
string const &, unsigned long int mode) const
|
||||
{
|
||||
return support::copy(from, to);
|
||||
return support::copy(from, to, mode);
|
||||
}
|
||||
|
||||
|
||||
@ -43,10 +45,19 @@ bool Mover::do_rename(string const & from, string const & to,
|
||||
|
||||
|
||||
bool SpecialisedMover::do_copy(string const & from, string const & to,
|
||||
string const & latex) const
|
||||
string const & latex, unsigned long int mode) const
|
||||
{
|
||||
if (command_.empty())
|
||||
return Mover::do_copy(from, to, latex);
|
||||
return Mover::do_copy(from, to, latex, mode);
|
||||
|
||||
if (mode != (unsigned long int)-1) {
|
||||
std::ofstream ofs(to.c_str(), ios::binary | ios::out | ios::trunc);
|
||||
if (!ofs)
|
||||
return false;
|
||||
ofs.close();
|
||||
if (!support::chmod(to.c_str(), mode_t(mode)))
|
||||
return false;
|
||||
}
|
||||
|
||||
string command = support::libScriptSearch(command_);
|
||||
command = support::subst(command, "$$i", from);
|
||||
@ -64,7 +75,7 @@ bool SpecialisedMover::do_rename(string const & from, string const & to,
|
||||
if (command_.empty())
|
||||
return Mover::do_rename(from, to, latex);
|
||||
|
||||
if (!do_copy(from, to, latex))
|
||||
if (!do_copy(from, to, latex, (unsigned long int)-1))
|
||||
return false;
|
||||
return support::unlink(from) == 0;
|
||||
}
|
||||
|
14
src/mover.h
14
src/mover.h
@ -34,9 +34,10 @@ public:
|
||||
* \returns true if successful.
|
||||
*/
|
||||
bool
|
||||
copy(std::string const & from, std::string const & to) const
|
||||
copy(std::string const & from, std::string const & to,
|
||||
unsigned long int mode = (unsigned long int)-1) const
|
||||
{
|
||||
return do_copy(from, to, to);
|
||||
return do_copy(from, to, to, mode);
|
||||
}
|
||||
|
||||
/** Copy file @c from to @c to.
|
||||
@ -49,9 +50,10 @@ public:
|
||||
*/
|
||||
bool
|
||||
copy(std::string const & from, std::string const & to,
|
||||
std::string const & latex) const
|
||||
std::string const & latex,
|
||||
unsigned long int mode = (unsigned long int)-1) const
|
||||
{
|
||||
return do_copy(from, to, latex);
|
||||
return do_copy(from, to, latex, mode);
|
||||
}
|
||||
|
||||
/** Rename file @c from as @c to.
|
||||
@ -84,7 +86,7 @@ public:
|
||||
protected:
|
||||
virtual bool
|
||||
do_copy(std::string const & from, std::string const & to,
|
||||
std::string const &) const;
|
||||
std::string const &, unsigned long int mode) const;
|
||||
|
||||
virtual bool
|
||||
do_rename(std::string const & from, std::string const & to,
|
||||
@ -131,7 +133,7 @@ public:
|
||||
private:
|
||||
virtual bool
|
||||
do_copy(std::string const & from, std::string const & to,
|
||||
std::string const & latex) const;
|
||||
std::string const & latex, unsigned long int mode) const;
|
||||
|
||||
virtual bool
|
||||
do_rename(std::string const & from, std::string const & to,
|
||||
|
@ -14,6 +14,13 @@
|
||||
|
||||
#include "support/lyxlib.h"
|
||||
|
||||
#ifdef HAVE_SYS_STAT_H
|
||||
# include <sys/stat.h>
|
||||
#endif
|
||||
#ifdef HAVE_SYS_TYPES_H
|
||||
# include <sys/types.h>
|
||||
#endif
|
||||
|
||||
|
||||
namespace lyx {
|
||||
|
||||
@ -24,12 +31,35 @@ using std::ios;
|
||||
using std::string;
|
||||
|
||||
|
||||
bool lyx::support::copy(string const & from, string const & to)
|
||||
bool lyx::support::chmod(string const & file, unsigned long int mode)
|
||||
{
|
||||
#ifdef HAVE_CHMOD
|
||||
if (::chmod(file.c_str(), mode_t(mode)) != 0)
|
||||
return false;
|
||||
#else
|
||||
# ifdef WITH_WARNINGS
|
||||
# warning "File permissions are ignored on this system."
|
||||
# endif
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
bool lyx::support::copy(string const & from, string const & to, unsigned long int mode)
|
||||
{
|
||||
ifstream ifs(from.c_str(), ios::binary | ios::in);
|
||||
if (!ifs)
|
||||
return false;
|
||||
|
||||
if (mode != (unsigned long int)-1) {
|
||||
ofstream ofs(to.c_str(), ios::binary | ios::out | ios::trunc);
|
||||
if (!ofs)
|
||||
return false;
|
||||
ofs.close();
|
||||
if (!chmod(to, mode_t(mode)))
|
||||
return false;
|
||||
}
|
||||
|
||||
ofstream ofs(to.c_str(), ios::binary | ios::out | ios::trunc);
|
||||
if (!ofs)
|
||||
return false;
|
||||
|
@ -25,13 +25,16 @@ namespace support {
|
||||
std::string const getcwd();
|
||||
/// change to a directory, 0 is returned on success.
|
||||
int chdir(std::string const & name);
|
||||
/// Change file permissions
|
||||
bool chmod(std::string const & file, unsigned long int mode);
|
||||
/**
|
||||
* rename a file, returns false if it fails.
|
||||
* It can handle renames across partitions.
|
||||
*/
|
||||
bool rename(std::string const & from, std::string const & to);
|
||||
/// copy a file, returns false it it fails
|
||||
bool copy(std::string const & from, std::string const & to);
|
||||
bool copy(std::string const & from, std::string const & to,
|
||||
unsigned long int mode = (unsigned long int)-1);
|
||||
/// generates a checksum of a file
|
||||
unsigned long sum(std::string const & file);
|
||||
/// FIXME: some point to this hmm ?
|
||||
|
@ -39,6 +39,9 @@ int lyx::support::mkdir(std::string const & pathname, unsigned long int mode)
|
||||
# if MKDIR_TAKES_ONE_ARG
|
||||
// MinGW32
|
||||
return ::mkdir(pathname.c_str());
|
||||
# ifdef WITH_WARNINGS
|
||||
# warning "Permissions of created directories are ignored on this system."
|
||||
# endif
|
||||
# else
|
||||
// POSIX
|
||||
return ::mkdir(pathname.c_str(), mode_t(mode));
|
||||
@ -46,8 +49,14 @@ int lyx::support::mkdir(std::string const & pathname, unsigned long int mode)
|
||||
#elif defined(_WIN32)
|
||||
// plain Windows 32
|
||||
return CreateDirectory(pathname.c_str(), 0) != 0 ? 0 : -1;
|
||||
# ifdef WITH_WARNINGS
|
||||
# warning "Permissions of created directories are ignored on this system."
|
||||
# endif
|
||||
#elif HAVE__MKDIR
|
||||
return ::_mkdir(pathname.c_str());
|
||||
# ifdef WITH_WARNINGS
|
||||
# warning "Permissions of created directories are ignored on this system."
|
||||
# endif
|
||||
#else
|
||||
# error "Don't know how to create a directory on this system."
|
||||
#endif
|
||||
|
Loading…
Reference in New Issue
Block a user