mirror of
https://github.com/OlafvdSpek/ctemplate.git
synced 2025-10-05 19:16:54 +08:00

another template has been added with the same name earlier in the search path, even if the original file is NOT updated(touched, updated, deleted etc). Tested: blaze test template:all R=panicker,csilvers DELTA=45 (6 added, 24 deleted, 15 changed) Revision created by MOE tool push_codebase. MOE_MIGRATION=3885
786 lines
29 KiB
C++
786 lines
29 KiB
C++
// Copyright (c) 2009, Google Inc.
|
|
// All rights reserved.
|
|
//
|
|
// Redistribution and use in source and binary forms, with or without
|
|
// modification, are permitted provided that the following conditions are
|
|
// met:
|
|
//
|
|
// * Redistributions of source code must retain the above copyright
|
|
// notice, this list of conditions and the following disclaimer.
|
|
// * Redistributions in binary form must reproduce the above
|
|
// copyright notice, this list of conditions and the following disclaimer
|
|
// in the documentation and/or other materials provided with the
|
|
// distribution.
|
|
// * Neither the name of Google Inc. nor the names of its
|
|
// contributors may be used to endorse or promote products derived from
|
|
// this software without specific prior written permission.
|
|
//
|
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
|
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
|
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
|
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
|
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
|
|
// ---
|
|
|
|
#include <config.h>
|
|
#include "base/mutex.h" // This must go first so we get _XOPEN_SOURCE
|
|
#include <ctemplate/template_cache.h>
|
|
#include <assert.h> // for assert()
|
|
#include <errno.h>
|
|
#include <stddef.h> // for size_t
|
|
#include <stdlib.h> // for strerror()
|
|
#include <sys/stat.h>
|
|
#ifdef HAVE_UNISTD_H
|
|
# include <unistd.h>
|
|
#endif // for getcwd()
|
|
#include HASH_MAP_H // for hash_map<>::iterator, hash_map<>, etc
|
|
#include <utility> // for pair<>, make_pair()
|
|
#include <vector> // for vector<>::size_type, vector<>, etc
|
|
#include "base/thread_annotations.h" // for GUARDED_BY
|
|
#include <ctemplate/template.h> // for Template, TemplateState
|
|
#include <ctemplate/template_enums.h> // for Strip, DO_NOT_STRIP
|
|
#include <ctemplate/template_pathops.h> // for PathJoin(), IsAbspath(), etc
|
|
#include <ctemplate/template_string.h> // for StringHash
|
|
#include "base/fileutil.h"
|
|
#include <iostream> // for cerr
|
|
|
|
#ifndef PATH_MAX
|
|
#ifdef MAXPATHLEN
|
|
#define PATH_MAX MAXPATHLEN
|
|
#else
|
|
#define PATH_MAX 4096 // seems conservative for max filename len!
|
|
#endif
|
|
#endif
|
|
|
|
using std::endl;
|
|
using std::string;
|
|
using std::vector;
|
|
using std::pair;
|
|
using std::make_pair;
|
|
#ifdef HAVE_UNORDERED_MAP
|
|
using HASH_NAMESPACE::unordered_map;
|
|
// This is totally cheap, but minimizes the need for #ifdef's below...
|
|
#define hash_map unordered_map
|
|
#else
|
|
using HASH_NAMESPACE::hash_map;
|
|
#endif
|
|
|
|
static int kVerbosity = 0; // you can change this by hand to get vlogs
|
|
#define LOG(level) std::cerr << #level ": "
|
|
#define PLOG(level) std::cerr << #level ": [" << strerror(errno) << "] "
|
|
#define VLOG(level) if (kVerbosity >= level) std::cerr << "V" #level ": "
|
|
|
|
_START_GOOGLE_NAMESPACE_
|
|
|
|
// ----------------------------------------------------------------------
|
|
// TemplateCache::RefcountedTemplate
|
|
// A simple refcounting class to keep track of templates, which
|
|
// might be shared between caches. It also owns the pointer to
|
|
// the template itself.
|
|
// ----------------------------------------------------------------------
|
|
|
|
class TemplateCache::RefcountedTemplate {
|
|
public:
|
|
explicit RefcountedTemplate(const Template* ptr) : ptr_(ptr), refcount_(1) { }
|
|
void IncRef() {
|
|
MutexLock ml(&mutex_);
|
|
assert(refcount_ > 0);
|
|
++refcount_;
|
|
}
|
|
void DecRefN(int n) {
|
|
bool refcount_is_zero;
|
|
{
|
|
MutexLock ml(&mutex_);
|
|
assert(refcount_ >= n);
|
|
refcount_ -= n;
|
|
refcount_is_zero = (refcount_ == 0);
|
|
}
|
|
// We can't delete this within the MutexLock, because when the
|
|
// MutexLock tries to unlock Mutex at function-exit, the mutex
|
|
// will have been deleted! This is just as safe as doing the
|
|
// delete within the lock -- in either case, if anyone tried to do
|
|
// anything to this class after the refcount got to 0, bad things
|
|
// would happen.
|
|
if (refcount_is_zero)
|
|
delete this;
|
|
}
|
|
void DecRef() {
|
|
DecRefN(1);
|
|
}
|
|
int refcount() const {
|
|
MutexLock ml(&mutex_); // could be ReaderMutexLock, but whatever
|
|
return refcount_;
|
|
}
|
|
const Template* tpl() const { return ptr_; }
|
|
|
|
private:
|
|
~RefcountedTemplate() { delete ptr_; }
|
|
const Template* const ptr_;
|
|
int refcount_ GUARDED_BY(mutex_);
|
|
mutable Mutex mutex_;
|
|
};
|
|
|
|
// ----------------------------------------------------------------------
|
|
// TemplateCache::RefTplPtrHash
|
|
// TemplateCache::TemplateCacheHash
|
|
// TemplateCache::CachedTemplate
|
|
// These are used for the cache-map. CachedTemplate is what is
|
|
// actually stored in the map: the Template* and some information
|
|
// about it (whether we need to reload it, etc.). Refcount is
|
|
// a simple refcounting class, used to keep track of templates.
|
|
// ----------------------------------------------------------------------
|
|
|
|
// This is needed just because many STLs (eg FreeBSD's) are unable to
|
|
// hash pointers by default.
|
|
class TemplateCache::RefTplPtrHash {
|
|
public:
|
|
size_t operator()(const RefcountedTemplate* p) const {
|
|
return reinterpret_cast<size_t>(p);
|
|
}
|
|
// Less operator for MSVC's hash containers.
|
|
bool operator()(const RefcountedTemplate* a,
|
|
const RefcountedTemplate* b) const {
|
|
return a < b;
|
|
}
|
|
// These two public members are required by msvc. 4 and 8 are defaults.
|
|
static const size_t bucket_size = 4;
|
|
static const size_t min_buckets = 8;
|
|
};
|
|
|
|
class TemplateCache::TemplateCacheHash {
|
|
public:
|
|
size_t operator()(const TemplateCacheKey& p) const {
|
|
// Using + here is silly, but should work ok in practice.
|
|
return p.first + p.second;
|
|
}
|
|
// Less operator for MSVC's hash containers.
|
|
bool operator()(const TemplateCacheKey& a,
|
|
const TemplateCacheKey& b) const {
|
|
return (a.first == b.first
|
|
? a.second < b.second
|
|
: a.first < b.first);
|
|
}
|
|
// These two public members are required by msvc. 4 and 8 are defaults.
|
|
static const size_t bucket_size = 4;
|
|
static const size_t min_buckets = 8;
|
|
};
|
|
|
|
struct TemplateCache::CachedTemplate {
|
|
enum TemplateType { UNUSED, FILE_BASED, STRING_BASED };
|
|
CachedTemplate()
|
|
: refcounted_tpl(NULL),
|
|
should_reload(false),
|
|
template_type(UNUSED) {
|
|
}
|
|
CachedTemplate(const Template* tpl_ptr, TemplateType type)
|
|
: refcounted_tpl(new TemplateCache::RefcountedTemplate(tpl_ptr)),
|
|
should_reload(false),
|
|
template_type(type) {
|
|
}
|
|
|
|
// we won't remove the template from the cache until refcount drops to 0
|
|
TemplateCache::RefcountedTemplate* refcounted_tpl; // shared across Clone()
|
|
// reload status
|
|
bool should_reload;
|
|
// indicates if the template is string-based or file-based
|
|
TemplateType template_type;
|
|
};
|
|
|
|
|
|
// ----------------------------------------------------------------------
|
|
// TemplateCache::TemplateCache()
|
|
// TemplateCache::~TemplateCache()
|
|
// ----------------------------------------------------------------------
|
|
|
|
TemplateCache::TemplateCache()
|
|
: parsed_template_cache_(new TemplateMap),
|
|
is_frozen_(false),
|
|
search_path_(),
|
|
get_template_calls_(new TemplateCallMap),
|
|
mutex_(new Mutex),
|
|
search_path_mutex_(new Mutex) {
|
|
}
|
|
|
|
TemplateCache::~TemplateCache() {
|
|
ClearCache();
|
|
delete parsed_template_cache_;
|
|
delete get_template_calls_;
|
|
delete mutex_;
|
|
delete search_path_mutex_;
|
|
}
|
|
|
|
|
|
// ----------------------------------------------------------------------
|
|
// HasTemplateChangedOnDisk
|
|
// Indicates whether the template has changed, based on the
|
|
// backing file's last modtime.
|
|
// ----------------------------------------------------------------------
|
|
|
|
bool HasTemplateChangedOnDisk(const char* resolved_filename,
|
|
time_t mtime,
|
|
FileStat* statbuf) {
|
|
if (!File::Stat(resolved_filename, statbuf)) {
|
|
LOG(WARNING) << "Unable to stat file " << resolved_filename << endl;
|
|
// If we can't Stat the file then the file may have been deleted,
|
|
// so reload the template.
|
|
return true;
|
|
}
|
|
if (statbuf->mtime == mtime && mtime > 0) {
|
|
// No need to reload yet.
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
|
|
// ----------------------------------------------------------------------
|
|
// TemplateCache::LoadTemplate()
|
|
// TemplateCache::GetTemplate()
|
|
// TemplateCache::GetTemplateLocked()
|
|
// TemplateCache::StringToTemplateCache()
|
|
// The routines for adding a template to the cache. LoadTemplate
|
|
// loads the template into the cache and returns true if the
|
|
// template was successfully loaded or if it already exists in the
|
|
// cache. GetTemplate loads the template into the cache from disk
|
|
// and returns the parsed template. StringToTemplateCache parses
|
|
// and loads the template from the given string into the parsed
|
|
// cache, or returns false if an older version already exists in
|
|
// the cache.
|
|
// ----------------------------------------------------------------------
|
|
|
|
bool TemplateCache::LoadTemplate(const TemplateString& filename, Strip strip) {
|
|
TemplateCacheKey cache_key = TemplateCacheKey(filename.GetGlobalId(), strip);
|
|
WriterMutexLock ml(mutex_);
|
|
return GetTemplateLocked(filename, strip, cache_key) != NULL;
|
|
}
|
|
|
|
const Template *TemplateCache::GetTemplate(const TemplateString& filename,
|
|
Strip strip) {
|
|
// No need to have the cache-mutex acquired for this step
|
|
TemplateCacheKey cache_key = TemplateCacheKey(filename.GetGlobalId(), strip);
|
|
CachedTemplate retval;
|
|
WriterMutexLock ml(mutex_);
|
|
RefcountedTemplate* refcounted_tpl =
|
|
GetTemplateLocked(filename, strip, cache_key);
|
|
if (!refcounted_tpl)
|
|
return NULL;
|
|
|
|
refcounted_tpl->IncRef(); // DecRef() is in DoneWithGetTemplatePtrs()
|
|
(*get_template_calls_)[refcounted_tpl]++; // set up for DoneWith...()
|
|
return refcounted_tpl->tpl();
|
|
}
|
|
|
|
TemplateCache::RefcountedTemplate* TemplateCache::GetTemplateLocked(
|
|
const TemplateString& filename,
|
|
Strip strip,
|
|
const TemplateCacheKey& template_cache_key) {
|
|
// NOTE: A write-lock must be held on mutex_ when this method is called.
|
|
TemplateMap::iterator it = parsed_template_cache_->find(template_cache_key);
|
|
if (it == parsed_template_cache_->end()) {
|
|
// If the cache is frozen and the template doesn't already exist in cache,
|
|
// do not load the template, return NULL.
|
|
if (is_frozen_) {
|
|
return NULL;
|
|
}
|
|
// TODO(panicker): Validate the filename here, and if the file can't be
|
|
// resolved then insert a NULL in the cache.
|
|
// If validation succeeds then pass in resolved filename, mtime &
|
|
// file length (from statbuf) to the constructor.
|
|
const Template* tpl = new Template(filename, strip, this);
|
|
it = parsed_template_cache_->insert(
|
|
make_pair(template_cache_key,
|
|
CachedTemplate(tpl, CachedTemplate::FILE_BASED))).first;
|
|
assert(it != parsed_template_cache_->end());
|
|
}
|
|
if (it->second.should_reload) {
|
|
// check if the template has changed on disk or if a new template with the
|
|
// same name has been added earlier in the search path:
|
|
const string resolved = FindTemplateFilename(
|
|
it->second.refcounted_tpl->tpl()->original_filename());
|
|
FileStat statbuf;
|
|
if (it->second.template_type == CachedTemplate::FILE_BASED &&
|
|
(resolved != it->second.refcounted_tpl->tpl()->template_file() ||
|
|
HasTemplateChangedOnDisk(
|
|
it->second.refcounted_tpl->tpl()->template_file(),
|
|
it->second.refcounted_tpl->tpl()->mtime(),
|
|
&statbuf))) {
|
|
// Create a new template, insert it into the cache under
|
|
// template_cache_key, and DecRef() the old one to indicate
|
|
// the cache no longer has a reference to it.
|
|
const Template* tpl = new Template(filename, strip, this);
|
|
// DecRef after creating the new template since DecRef may free up
|
|
// the storage for filename,
|
|
it->second.refcounted_tpl->DecRef();
|
|
it->second = CachedTemplate(tpl, CachedTemplate::FILE_BASED);
|
|
}
|
|
it->second.should_reload = false;
|
|
}
|
|
|
|
// If the state is TS_ERROR, we leave the state as is, but return
|
|
// NULL. We won't try to load the template file again until the
|
|
// reload status is set to true by another call to ReloadAllIfChanged.
|
|
if (it->second.refcounted_tpl->tpl()->state() != TS_READY) {
|
|
return NULL;
|
|
} else {
|
|
return it->second.refcounted_tpl;
|
|
}
|
|
}
|
|
|
|
bool TemplateCache::StringToTemplateCache(const TemplateString& key,
|
|
const TemplateString& content,
|
|
Strip strip) {
|
|
TemplateCacheKey template_cache_key = TemplateCacheKey(key.GetGlobalId(),
|
|
strip);
|
|
{
|
|
ReaderMutexLock ml(mutex_);
|
|
if (is_frozen_) {
|
|
return false;
|
|
}
|
|
// If the key is already in the parsed-cache, we just return false.
|
|
TemplateMap::iterator it = parsed_template_cache_->find(template_cache_key);
|
|
if (it != parsed_template_cache_->end() &&
|
|
it->second.refcounted_tpl->tpl()->state() != TS_ERROR) {
|
|
return false;
|
|
}
|
|
}
|
|
Template* tpl = Template::StringToTemplate(content, strip);
|
|
if (tpl == NULL) {
|
|
return false;
|
|
}
|
|
if (tpl->state() != TS_READY) {
|
|
delete tpl;
|
|
return false;
|
|
}
|
|
|
|
WriterMutexLock ml(mutex_);
|
|
// Double-check it wasn't just inserted.
|
|
TemplateMap::iterator it = parsed_template_cache_->find(template_cache_key);
|
|
if (it != parsed_template_cache_->end()) {
|
|
if (it->second.refcounted_tpl->tpl()->state() == TS_ERROR) {
|
|
// replace the old entry with the new one
|
|
it->second.refcounted_tpl->DecRef();
|
|
} else {
|
|
delete tpl;
|
|
return false;
|
|
}
|
|
}
|
|
// Insert into cache.
|
|
(*parsed_template_cache_)[template_cache_key] =
|
|
CachedTemplate(tpl, CachedTemplate::STRING_BASED);
|
|
return true;
|
|
}
|
|
|
|
// ----------------------------------------------------------------------
|
|
// TemplateCache::ExpandWithData()
|
|
// TemplateCache::ExpandFrozen()
|
|
// TemplateCache::ExpandLocked()
|
|
// ExpandWithData gets the template from the parsed-cache, possibly
|
|
// loading the template on-demand, and then expands the template.
|
|
// ExpandFrozen is for frozen caches only -- if the filename isn't
|
|
// in the cache, the routine fails (returns false) rather than trying
|
|
// to fetch the template. ExpandLocked is used for recursive
|
|
// sub-template includes, and just tells template.cc it doesn't
|
|
// need to recursively acquire any locks.
|
|
// ----------------------------------------------------------------------
|
|
|
|
bool TemplateCache::ExpandWithData(const TemplateString& filename,
|
|
Strip strip,
|
|
const TemplateDictionaryInterface *dict,
|
|
PerExpandData *per_expand_data,
|
|
ExpandEmitter *expand_emitter) {
|
|
TemplateCacheKey template_cache_key(filename.GetGlobalId(), strip);
|
|
// We make a local copy of this struct so we don't have to worry about
|
|
// what happens to our cache while we don't hold the lock (during Expand).
|
|
RefcountedTemplate* refcounted_tpl = NULL;
|
|
{
|
|
WriterMutexLock ml(mutex_);
|
|
// Optionally load the template (depending on whether the cache is frozen,
|
|
// the reload bit is set etc.)
|
|
refcounted_tpl = GetTemplateLocked(filename, strip, template_cache_key);
|
|
if (!refcounted_tpl)
|
|
return false;
|
|
refcounted_tpl->IncRef();
|
|
}
|
|
const bool result = refcounted_tpl->tpl()->ExpandWithDataAndCache(
|
|
expand_emitter, dict, per_expand_data, this);
|
|
{
|
|
WriterMutexLock ml(mutex_);
|
|
refcounted_tpl->DecRef();
|
|
}
|
|
return result;
|
|
}
|
|
|
|
bool TemplateCache::ExpandNoLoad(
|
|
const TemplateString& filename,
|
|
Strip strip,
|
|
const TemplateDictionaryInterface *dict,
|
|
PerExpandData *per_expand_data,
|
|
ExpandEmitter *expand_emitter) const {
|
|
TemplateCacheKey template_cache_key(filename.GetGlobalId(), strip);
|
|
CachedTemplate cached_tpl;
|
|
TemplateMap::iterator it;
|
|
{
|
|
ReaderMutexLock ml(mutex_);
|
|
if (!is_frozen_) {
|
|
LOG(DFATAL) << ": ExpandNoLoad() only works on frozen caches.";
|
|
return false;
|
|
}
|
|
it = parsed_template_cache_->find(template_cache_key);
|
|
if (it == parsed_template_cache_->end()) {
|
|
return false;
|
|
}
|
|
cached_tpl = it->second;
|
|
cached_tpl.refcounted_tpl->IncRef();
|
|
}
|
|
const bool result = cached_tpl.refcounted_tpl->tpl()->ExpandWithDataAndCache(
|
|
expand_emitter, dict, per_expand_data, this);
|
|
{
|
|
WriterMutexLock ml(mutex_);
|
|
cached_tpl.refcounted_tpl->DecRef();
|
|
}
|
|
return result;
|
|
}
|
|
|
|
// Note: "Locked" in this name refers to the template object, not to
|
|
// use; we still need to acquire our locks as per normal.
|
|
bool TemplateCache::ExpandLocked(const TemplateString& filename,
|
|
Strip strip,
|
|
ExpandEmitter *expand_emitter,
|
|
const TemplateDictionaryInterface *dict,
|
|
PerExpandData *per_expand_data) {
|
|
TemplateCacheKey template_cache_key(filename.GetGlobalId(), strip);
|
|
RefcountedTemplate* refcounted_tpl = NULL;
|
|
{
|
|
WriterMutexLock ml(mutex_);
|
|
refcounted_tpl = GetTemplateLocked(filename, strip, template_cache_key);
|
|
if (!refcounted_tpl)
|
|
return false;
|
|
refcounted_tpl->IncRef();
|
|
}
|
|
const bool result = refcounted_tpl->tpl()->ExpandLocked(
|
|
expand_emitter, dict, per_expand_data, this);
|
|
{
|
|
WriterMutexLock ml(mutex_);
|
|
refcounted_tpl->DecRef();
|
|
}
|
|
return result;
|
|
}
|
|
|
|
// ----------------------------------------------------------------------
|
|
// TemplateCache::SetTemplateRootDirectory()
|
|
// TemplateCache::AddAlternateTemplateRootDirectory()
|
|
// TemplateCache::template_root_directory()
|
|
// TemplateCache::FindTemplateFilename()
|
|
// The template-root-directory is where we look for template
|
|
// files (in GetTemplate and include templates) when they're
|
|
// given with a relative rather than absolute name. You can
|
|
// set a 'main' root directory (where we look first), as well
|
|
// as alternates.
|
|
// ----------------------------------------------------------------------
|
|
|
|
bool TemplateCache::AddAlternateTemplateRootDirectoryHelper(
|
|
const string& directory,
|
|
bool clear_template_search_path) {
|
|
{
|
|
ReaderMutexLock ml(mutex_);
|
|
if (is_frozen_) { // Cannot set root-directory on a frozen cache.
|
|
return false;
|
|
}
|
|
}
|
|
string normalized = directory;
|
|
// make sure it ends with '/'
|
|
NormalizeDirectory(&normalized);
|
|
// Make the directory absolute if it isn't already. This makes code
|
|
// safer if client later does a chdir.
|
|
if (!IsAbspath(normalized)) {
|
|
char* cwdbuf = new char[PATH_MAX]; // new to avoid stack overflow
|
|
const char* cwd = getcwd(cwdbuf, PATH_MAX);
|
|
if (!cwd) { // probably not possible, but best to be defensive
|
|
PLOG(WARNING) << "Unable to convert '" << normalized
|
|
<< "' to an absolute path, with cwd=" << cwdbuf;
|
|
} else {
|
|
normalized = PathJoin(cwd, normalized);
|
|
}
|
|
delete[] cwdbuf;
|
|
}
|
|
|
|
VLOG(2) << "Setting Template directory to " << normalized << endl;
|
|
{
|
|
WriterMutexLock ml(search_path_mutex_);
|
|
if (clear_template_search_path) {
|
|
search_path_.clear();
|
|
}
|
|
search_path_.push_back(normalized);
|
|
}
|
|
|
|
// NOTE(williasr): The template root is not part of the template
|
|
// cache key, so we need to invalidate the cache contents.
|
|
ReloadAllIfChanged(LAZY_RELOAD);
|
|
return true;
|
|
}
|
|
|
|
bool TemplateCache::SetTemplateRootDirectory(const string& directory) {
|
|
return AddAlternateTemplateRootDirectoryHelper(directory, true);
|
|
}
|
|
|
|
bool TemplateCache::AddAlternateTemplateRootDirectory(
|
|
const string& directory) {
|
|
return AddAlternateTemplateRootDirectoryHelper(directory, false);
|
|
}
|
|
|
|
string TemplateCache::template_root_directory() const {
|
|
ReaderMutexLock ml(search_path_mutex_);
|
|
if (search_path_.empty()) {
|
|
return kCWD;
|
|
}
|
|
return search_path_[0];
|
|
}
|
|
|
|
// Given an unresolved filename, look through the template search path
|
|
// to see if the template can be found. If so, resolved contains the
|
|
// resolved filename, statbuf contains the stat structure for the file
|
|
// (to avoid double-statting the file), and the function returns
|
|
// true. Otherwise, the function returns false.
|
|
bool TemplateCache::ResolveTemplateFilename(const string& unresolved,
|
|
string* resolved,
|
|
FileStat* statbuf) const {
|
|
ReaderMutexLock ml(search_path_mutex_);
|
|
if (search_path_.empty() || IsAbspath(unresolved)) {
|
|
*resolved = unresolved;
|
|
if (File::Stat(*resolved, statbuf)) {
|
|
VLOG(1) << "Resolved " << unresolved << " to " << *resolved << endl;
|
|
return true;
|
|
}
|
|
} else {
|
|
for (TemplateSearchPath::const_iterator path = search_path_.begin();
|
|
path != search_path_.end();
|
|
++path) {
|
|
*resolved = PathJoin(*path, unresolved);
|
|
if (File::Stat(*resolved, statbuf)) {
|
|
VLOG(1) << "Resolved " << unresolved << " to " << *resolved << endl;
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
resolved->clear();
|
|
return false;
|
|
}
|
|
|
|
string TemplateCache::FindTemplateFilename(const string& unresolved)
|
|
const {
|
|
string resolved;
|
|
FileStat statbuf;
|
|
if (!ResolveTemplateFilename(unresolved, &resolved, &statbuf))
|
|
resolved.clear();
|
|
return resolved;
|
|
}
|
|
|
|
|
|
// ----------------------------------------------------------------------
|
|
// TemplateCache::Delete()
|
|
// TemplateCache::ClearCache()
|
|
// Delete deletes one entry from the cache.
|
|
// ----------------------------------------------------------------------
|
|
|
|
bool TemplateCache::Delete(const TemplateString& key) {
|
|
WriterMutexLock ml(mutex_);
|
|
if (is_frozen_) { // Cannot delete from a frozen cache.
|
|
return false;
|
|
}
|
|
vector<TemplateCacheKey> to_erase;
|
|
const TemplateId key_id = key.GetGlobalId();
|
|
for (TemplateMap::iterator it = parsed_template_cache_->begin();
|
|
it != parsed_template_cache_->end(); ++it) {
|
|
if (it->first.first == key_id) {
|
|
// We'll delete the content pointed to by the entry here, since
|
|
// it's handy, but we won't delete the entry itself quite yet.
|
|
it->second.refcounted_tpl->DecRef();
|
|
to_erase.push_back(it->first);
|
|
}
|
|
}
|
|
for (vector<TemplateCacheKey>::iterator it = to_erase.begin();
|
|
it != to_erase.end(); ++it) {
|
|
parsed_template_cache_->erase(*it);
|
|
}
|
|
return !to_erase.empty();
|
|
}
|
|
|
|
void TemplateCache::ClearCache() {
|
|
// NOTE: We allow a frozen cache to be cleared with this method, although
|
|
// no other changes can be made to the cache.
|
|
// We clear the cache by swapping it with an empty cache. This lets
|
|
// us delete the items in the cache at our leisure without needing
|
|
// to hold mutex_.
|
|
TemplateMap tmp_cache;
|
|
{
|
|
WriterMutexLock ml(mutex_);
|
|
parsed_template_cache_->swap(tmp_cache);
|
|
is_frozen_ = false;
|
|
}
|
|
for (TemplateMap::iterator it = tmp_cache.begin();
|
|
it != tmp_cache.end();
|
|
++it) {
|
|
it->second.refcounted_tpl->DecRef();
|
|
}
|
|
|
|
// Do a decref for all templates ever returned by GetTemplate().
|
|
DoneWithGetTemplatePtrs();
|
|
}
|
|
|
|
// ----------------------------------------------------------------------
|
|
// TemplateCache::DoneWithGetTemplatePtrs()
|
|
// DoneWithGetTemplatePtrs() DecRefs every template in the
|
|
// get_template_calls_ list. This is because the user of
|
|
// GetTemplate() didn't have a pointer to the refcounted Template
|
|
// to do this themselves. Note we only provide this as a batch
|
|
// operation, so the user should be careful to only call this when
|
|
// they are no longer using *any* template ever retrieved by
|
|
// this cache's GetTemplate().
|
|
// ----------------------------------------------------------------------
|
|
|
|
void TemplateCache::DoneWithGetTemplatePtrs() {
|
|
WriterMutexLock ml(mutex_);
|
|
for (TemplateCallMap::iterator it = get_template_calls_->begin();
|
|
it != get_template_calls_->end(); ++it) {
|
|
it->first->DecRefN(it->second); // it.second: # of times GetTpl was called
|
|
}
|
|
get_template_calls_->clear();
|
|
}
|
|
|
|
// ----------------------------------------------------------------------
|
|
// TemplateCache::ReloadAllIfChanged()
|
|
// IMMEDIATE_RELOAD attempts to immediately reload and parse
|
|
// all templates if the corresponding template files have changed.
|
|
// LAZY_RELOAD just sets the reload bit in the cache so that the next
|
|
// GetTemplate will reload and parse the template, if it changed.
|
|
|
|
// NOTE: Suppose the search path is "dira:dirb", and a template is
|
|
// created with name "foo", which resolves to "dirb/foo" because
|
|
// dira/foo does not exist. Then suppose dira/foo is created and then
|
|
// ReloadAllIfChanged() is called. Then ReloadAllIfChanged() will replace
|
|
// the contents of the template with dira/foo, *not* dirb/foo, even if
|
|
// dirb/foo hasn't changed.
|
|
// ----------------------------------------------------------------------
|
|
|
|
void TemplateCache::ReloadAllIfChanged(ReloadType reload_type) {
|
|
WriterMutexLock ml(mutex_);
|
|
if (is_frozen_) { // do not reload a frozen cache.
|
|
return;
|
|
}
|
|
for (TemplateMap::iterator it = parsed_template_cache_->begin();
|
|
it != parsed_template_cache_->end();
|
|
++it) {
|
|
it->second.should_reload = true;
|
|
if (reload_type == IMMEDIATE_RELOAD) {
|
|
const Template* tpl = it->second.refcounted_tpl->tpl();
|
|
// Reload should always use the original filename.
|
|
// For instance on reload, we may replace an existing template with a
|
|
// new one that came earlier on the search path.
|
|
GetTemplateLocked(tpl->original_filename(), tpl->strip(), it->first);
|
|
}
|
|
}
|
|
}
|
|
|
|
// ----------------------------------------------------------------------
|
|
// TemplateCache::Freeze()
|
|
// This method marks the cache as 'frozen'. After this method is called,
|
|
// the cache is immutable, and cannot be modified. New templates cannot be
|
|
// loaded and existing templates cannot be reloaded.
|
|
// ----------------------------------------------------------------------
|
|
|
|
void TemplateCache::Freeze() {
|
|
{
|
|
ReaderMutexLock ml(mutex_);
|
|
if (is_frozen_) { // if already frozen, then this is a no-op.
|
|
return;
|
|
}
|
|
}
|
|
// A final reload before freezing the cache.
|
|
ReloadAllIfChanged(IMMEDIATE_RELOAD);
|
|
{
|
|
WriterMutexLock ml(mutex_);
|
|
is_frozen_ = true;
|
|
}
|
|
}
|
|
|
|
// ----------------------------------------------------------------------
|
|
// TemplateCache::Clone()
|
|
// Clone makes a shallow copy of the parsed cache by incrementing
|
|
// templates' refcount.
|
|
// The caller is responsible for deallocating the returned TemplateCache.
|
|
// ----------------------------------------------------------------------
|
|
|
|
TemplateCache* TemplateCache::Clone() const {
|
|
ReaderMutexLock ml(mutex_);
|
|
TemplateCache* new_cache = new TemplateCache();
|
|
*(new_cache->parsed_template_cache_) = *parsed_template_cache_;
|
|
for (TemplateMap::iterator it = parsed_template_cache_->begin();
|
|
it != parsed_template_cache_->end(); ++it) {
|
|
it->second.refcounted_tpl->IncRef();
|
|
}
|
|
|
|
return new_cache;
|
|
}
|
|
|
|
// ----------------------------------------------------------------------
|
|
// TemplateCache::Refcount()
|
|
// This routine is DEBUG-only. It returns the refcount of a template,
|
|
// given the TemplateCacheKey.
|
|
// ----------------------------------------------------------------------
|
|
|
|
int TemplateCache::Refcount(const TemplateCacheKey template_cache_key) const {
|
|
ReaderMutexLock ml(mutex_);
|
|
TemplateMap::const_iterator it =
|
|
parsed_template_cache_->find(template_cache_key);
|
|
if (it != parsed_template_cache_->end()) {
|
|
return it->second.refcounted_tpl->refcount();
|
|
} else {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
// ----------------------------------------------------------------------
|
|
// TemplateCache::TemplateIsCached()
|
|
// This routine is for testing only -- is says whether a given
|
|
// template is already in the cache or not.
|
|
// ----------------------------------------------------------------------
|
|
|
|
bool TemplateCache::TemplateIsCached(const TemplateCacheKey template_cache_key)
|
|
const {
|
|
ReaderMutexLock ml(mutex_);
|
|
return (parsed_template_cache_->find(template_cache_key) !=
|
|
parsed_template_cache_->end());
|
|
}
|
|
|
|
// ----------------------------------------------------------------------
|
|
// TemplateCache::ValidTemplateFilename
|
|
// Validates the filename before constructing the template.
|
|
// ----------------------------------------------------------------------
|
|
|
|
bool TemplateCache::IsValidTemplateFilename(const string& filename,
|
|
string* resolved_filename,
|
|
FileStat* statbuf) const {
|
|
if (!ResolveTemplateFilename(filename,
|
|
resolved_filename,
|
|
statbuf)) {
|
|
LOG(WARNING) << "Unable to locate file " << filename << endl;
|
|
return false;
|
|
}
|
|
if (statbuf->IsDirectory()) {
|
|
LOG(WARNING) << *resolved_filename
|
|
<< "is a directory and thus not readable" << endl;
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
_END_GOOGLE_NAMESPACE_
|