/* | |
* Copyright (C) 2003-2006 Ben van Klinken and the CLucene Team | |
* | |
* Distributable under the terms of either the Apache License (Version 2.0) or | |
* the GNU Lesser General Public License, as specified in the COPYING file. | |
* | |
* Changes are Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). | |
*/ | |
#include <QtCore/QDir> | |
#include <QtCore/QDateTime> | |
#include <QtCore/QFileInfo> | |
#include <QtCore/QByteArray> | |
#include <QtCore/QCryptographicHash> | |
#include "CLucene/StdHeader.h" | |
#include "FSDirectory.h" | |
#include "CLucene/index/IndexReader.h" | |
#include "CLucene/util/Misc.h" | |
#include "CLucene/debug/condition.h" | |
CL_NS_DEF(store) | |
CL_NS_USE(util) | |
bool FSDirectory::disableLocks = false; | |
// This cache of directories ensures that there is a unique Directory instance | |
// per path, so that synchronization on the Directory can be used to synchronize | |
// access between readers and writers. | |
static CL_NS(util)::CLHashMap<QString, FSDirectory*, | |
CL_NS(util)::Compare::Qstring, CL_NS(util)::Equals::Qstring, | |
CL_NS(util)::Deletor::DummyQString> DIRECTORIES(false, false); | |
// # pragma mark -- FSDirectory::FSLock | |
FSDirectory::FSLock::FSLock(const QString& _lockDir, const QString& name) | |
: lockDir(_lockDir) | |
, lockFile(_lockDir + QDir::separator() + name) | |
{ | |
} | |
FSDirectory::FSLock::~FSLock() | |
{ | |
} | |
bool FSDirectory::FSLock::obtain() | |
{ | |
if (disableLocks) | |
return true; | |
if (QFile::exists(lockFile)) | |
return false; | |
QDir dir(lockDir); | |
if (!dir.exists()) { | |
if (!dir.mkpath(lockDir)) { | |
// 34: len of "Couldn't create lock directory: " | |
char* err = _CL_NEWARRAY( | |
char, 34 + strlen(lockDir.toLocal8Bit().constData()) + 1); | |
strcpy(err, "Couldn't create lock directory: "); | |
strcat(err, lockDir.toLocal8Bit().constData()); | |
_CLTHROWA_DEL(CL_ERR_IO, err); | |
} | |
} | |
QFile file(lockFile); | |
return file.open(QIODevice::ReadWrite); | |
} | |
void FSDirectory::FSLock::release() | |
{ | |
if (disableLocks) | |
return; | |
QFile file(lockFile); | |
file.remove(); | |
} | |
bool FSDirectory::FSLock::isLocked() | |
{ | |
if (disableLocks) | |
return false; | |
return QFile::exists(lockFile); | |
} | |
QString FSDirectory::FSLock::toString() const | |
{ | |
QString ret(QLatin1String("Lock@")); | |
return ret.append(lockFile); | |
} | |
// # pragma mark -- FSDirectory::FSIndexInput | |
FSDirectory::FSIndexInput::FSIndexInput(const QString& path, int32_t bufferSize) | |
: BufferedIndexInput(bufferSize) | |
{ | |
CND_PRECONDITION(!path.isEmpty(), "path is NULL"); | |
handle = _CLNEW SharedHandle(); | |
handle->fhandle.setFileName(path); | |
handle->fhandle.open(QIODevice::ReadOnly); | |
if (handle->fhandle.error() != QFile::NoError) { | |
switch(handle->fhandle.error()) { | |
case 1: | |
_CLTHROWA(CL_ERR_IO, "An error occurred when reading from the file"); | |
break; | |
case 2: | |
_CLTHROWA(CL_ERR_IO, "An error occurred when writing to the file."); | |
break; | |
case 5: | |
_CLTHROWA(CL_ERR_IO, "The file could not be opened."); | |
break; | |
case 6: | |
_CLTHROWA(CL_ERR_IO, "The operation was aborted."); | |
break; | |
case 7: | |
_CLTHROWA(CL_ERR_IO, "A timeout occurred."); | |
break; | |
case 8: | |
_CLTHROWA(CL_ERR_IO, "An unspecified error occurred."); | |
break; | |
case 9: | |
_CLTHROWA(CL_ERR_IO, "The file could not be removed."); | |
break; | |
case 10: | |
_CLTHROWA(CL_ERR_IO, "The file could not be renamed."); | |
break; | |
case 11: | |
_CLTHROWA(CL_ERR_IO, "The position in the file could not be changed."); | |
break; | |
case 12: | |
_CLTHROWA(CL_ERR_IO, "The file could not be resized.e"); | |
break; | |
case 13: | |
_CLTHROWA(CL_ERR_IO, "The file could not be accessed."); | |
break; | |
case 14: | |
_CLTHROWA(CL_ERR_IO, "The file could not be copied."); | |
break; | |
case 4: | |
default: | |
_CLTHROWA(CL_ERR_IO, "A fatal error occurred."); | |
} | |
} | |
//Store the file length | |
handle->_length = handle->fhandle.size(); | |
handle->_fpos = 0; | |
this->_pos = 0; | |
} | |
FSDirectory::FSIndexInput::FSIndexInput(const FSIndexInput& other) | |
: BufferedIndexInput(other) | |
{ | |
if (other.handle == NULL) | |
_CLTHROWA(CL_ERR_NullPointer, "other handle is null"); | |
SCOPED_LOCK_MUTEX(*other.handle->THIS_LOCK) | |
_pos = other.handle->_fpos; | |
handle = _CL_POINTER(other.handle); | |
} | |
FSDirectory::FSIndexInput::~FSIndexInput() | |
{ | |
FSIndexInput::close(); | |
} | |
void FSDirectory::FSIndexInput::close() | |
{ | |
BufferedIndexInput::close(); | |
#ifdef _LUCENE_THREADMUTEX | |
if (handle != NULL) { | |
// Here we have a bit of a problem... We need to lock the handle to | |
// ensure that we can safely delete the handle... But if we delete the | |
// handle, then the scoped unlock, won't be able to unlock the mutex... | |
// take a reference of the lock object... | |
_LUCENE_THREADMUTEX* mutex = handle->THIS_LOCK; | |
//lock the mutex | |
mutex->lock(); | |
// determine if we are about to delete the handle... | |
bool doUnlock = (handle->__cl_refcount > 1); | |
// decdelete (deletes if refcount is down to 0) | |
_CLDECDELETE(handle); | |
if (doUnlock) | |
mutex->unlock(); | |
else | |
delete mutex; | |
} | |
#else | |
_CLDECDELETE(handle); | |
#endif | |
} | |
IndexInput* FSDirectory::FSIndexInput::clone() const | |
{ | |
return _CLNEW FSDirectory::FSIndexInput(*this); | |
} | |
void FSDirectory::FSIndexInput::seekInternal(const int64_t position) | |
{ | |
CND_PRECONDITION(position >= 0 && position < handle->_length, | |
"Seeking out of range") | |
_pos = position; | |
} | |
void FSDirectory::FSIndexInput::readInternal(uint8_t* b, const int32_t len) | |
{ | |
SCOPED_LOCK_MUTEX(*handle->THIS_LOCK) | |
CND_PRECONDITION(handle != NULL, "shared file handle has closed"); | |
CND_PRECONDITION(handle->fhandle.isOpen(), "file is not open"); | |
if (handle->_fpos != _pos) { | |
handle->fhandle.seek(_pos); | |
if (handle->fhandle.pos() != _pos) | |
_CLTHROWA( CL_ERR_IO, "File IO Seek error"); | |
handle->_fpos = _pos; | |
} | |
bufferLength = (int32_t)handle->fhandle.read((char*)b, len); | |
if (bufferLength == 0) | |
_CLTHROWA(CL_ERR_IO, "read past EOF"); | |
if (bufferLength == -1) | |
_CLTHROWA(CL_ERR_IO, "read error"); | |
_pos += bufferLength; | |
handle->_fpos =_pos; | |
} | |
// # pragma mark -- FSDirectory::FSIndexInput::SharedHandle | |
FSDirectory::FSIndexInput::SharedHandle::SharedHandle() | |
: _fpos(0) | |
, _length(0) | |
{ | |
#ifdef _LUCENE_THREADMUTEX | |
THIS_LOCK = new _LUCENE_THREADMUTEX; | |
#endif | |
} | |
FSDirectory::FSIndexInput::SharedHandle::~SharedHandle() | |
{ | |
if (fhandle.isOpen()) | |
fhandle.close(); | |
} | |
// # pragma mark -- FSDirectory::FSIndexOutput | |
FSDirectory::FSIndexOutput::FSIndexOutput(const QString& path) | |
{ | |
//O_BINARY - Opens file in binary (untranslated) mode | |
//O_CREAT - Creates and opens new file for writing. Has no effect if file specified by filename exists | |
//O_RANDOM - Specifies that caching is optimized for, but not restricted to, random access from disk. | |
//O_WRONLY - Opens file for writing only; | |
fhandle.setFileName(path); | |
fhandle.open(QIODevice::ReadWrite | QIODevice::Truncate); | |
if (fhandle.error() != QFile::NoError) { | |
switch(fhandle.error()) { | |
case 1: | |
_CLTHROWA(CL_ERR_IO, "An error occurred when reading from the file"); | |
break; | |
case 2: | |
_CLTHROWA(CL_ERR_IO, "An error occurred when writing to the file."); | |
break; | |
case 5: | |
_CLTHROWA(CL_ERR_IO, "The file could not be opened."); | |
break; | |
case 6: | |
_CLTHROWA(CL_ERR_IO, "The operation was aborted."); | |
break; | |
case 7: | |
_CLTHROWA(CL_ERR_IO, "A timeout occurred."); | |
break; | |
case 8: | |
_CLTHROWA(CL_ERR_IO, "An unspecified error occurred."); | |
break; | |
case 9: | |
_CLTHROWA(CL_ERR_IO, "The file could not be removed."); | |
break; | |
case 10: | |
_CLTHROWA(CL_ERR_IO, "The file could not be renamed."); | |
break; | |
case 11: | |
_CLTHROWA(CL_ERR_IO, "The position in the file could not be changed."); | |
break; | |
case 12: | |
_CLTHROWA(CL_ERR_IO, "The file could not be resized.e"); | |
break; | |
case 13: | |
_CLTHROWA(CL_ERR_IO, "The file could not be accessed."); | |
break; | |
case 14: | |
_CLTHROWA(CL_ERR_IO, "The file could not be copied."); | |
break; | |
case 4: | |
default: | |
_CLTHROWA(CL_ERR_IO, "A fatal error occurred."); | |
} | |
} | |
} | |
FSDirectory::FSIndexOutput::~FSIndexOutput() | |
{ | |
if (fhandle.isOpen()) { | |
try { | |
FSIndexOutput::close(); | |
} catch (CLuceneError& err) { | |
//ignore IO errors... | |
if (err.number() != CL_ERR_IO) | |
throw; | |
} | |
} | |
} | |
void FSDirectory::FSIndexOutput::close() | |
{ | |
try { | |
BufferedIndexOutput::close(); | |
} catch (CLuceneError& err) { | |
//ignore IO errors... | |
if (err.number() != CL_ERR_IO) | |
throw; | |
} | |
fhandle.close(); | |
} | |
int64_t FSDirectory::FSIndexOutput::length() | |
{ | |
CND_PRECONDITION(fhandle.isOpen(), "file is not open"); | |
return fhandle.size(); | |
} | |
void FSDirectory::FSIndexOutput::seek(const int64_t pos) | |
{ | |
CND_PRECONDITION(fhandle.isOpen(), "file is not open"); | |
BufferedIndexOutput::seek(pos); | |
fhandle.seek(pos); | |
if (fhandle.pos() != pos) | |
_CLTHROWA(CL_ERR_IO, "File IO Seek error"); | |
} | |
void FSDirectory::FSIndexOutput::flushBuffer(const uint8_t* b, const int32_t size) | |
{ | |
CND_PRECONDITION(fhandle.isOpen(), "file is not open"); | |
if (size > 0 && fhandle.write((const char*)b, size) != size) | |
_CLTHROWA(CL_ERR_IO, "File IO Write error"); | |
} | |
// # pragma mark -- FSDirectory | |
FSDirectory::FSDirectory(const QString& path, const bool createDir) | |
: Directory() | |
, refCount(0) | |
, useMMap(false) | |
{ | |
//set a realpath so that if we change directory, we can still function | |
directory = QFileInfo(path).absoluteFilePath(); | |
lockDir = directory; | |
QDir dir(lockDir); | |
if (!dir.exists()) { | |
if (!dir.mkpath(lockDir)) | |
_CLTHROWA_DEL(CL_ERR_IO, "Cannot create temp directory"); | |
} | |
QFileInfo info(lockDir); | |
if (info.isFile() || info.isSymLink()) | |
_CLTHROWA(CL_ERR_IO, "Found regular file where directory expected"); | |
if (createDir) | |
create(); | |
dir.setPath(directory); | |
if (!dir.exists()) { | |
//19: len of " is not a directory" | |
char* err = | |
_CL_NEWARRAY(char, 19 + strlen(path.toLocal8Bit().constData()) + 1); | |
strcpy(err, path.toLocal8Bit().constData()); | |
strcat(err, " is not a directory"); | |
_CLTHROWA_DEL(CL_ERR_IO, err); | |
} | |
} | |
void FSDirectory::create() | |
{ | |
SCOPED_LOCK_MUTEX(THIS_LOCK) | |
bool clear = false; | |
QDir dir(directory); | |
if (!dir.exists()) { | |
if (!dir.mkpath(directory)) { | |
char* err = _CL_NEWARRAY( // 27 len of "Couldn't create directory:" | |
char, 27 + strlen(directory.toLocal8Bit().constData()) + 1); | |
strcpy(err, "Couldn't create directory: "); | |
strcat(err, directory.toLocal8Bit().constData()); | |
_CLTHROWA_DEL(CL_ERR_IO, err); | |
} | |
} else { | |
clear = true; | |
} | |
QFileInfo info(directory); | |
if (info.isFile() || info.isSymLink()) { | |
char tmp[1024]; | |
_snprintf(tmp, 1024, "%s not a directory", | |
directory.toLocal8Bit().constData()); | |
_CLTHROWA(CL_ERR_IO, tmp); | |
} | |
if (clear) { | |
dir.setPath(directory); | |
// clear probably existing lucene index files | |
QStringList fileList = dir.entryList(QDir::Files | QDir::Hidden | |
| QDir::NoSymLinks); | |
foreach(const QString file, fileList) { | |
if (CL_NS(index)::IndexReader::isLuceneFile(file)) { | |
if (!dir.remove(file)) | |
_CLTHROWA(CL_ERR_IO, "Couldn't delete file "); | |
} | |
} | |
// clear probably existing file locks | |
QFileInfo dirInfo(lockDir); | |
if (dirInfo.exists() && dirInfo.isReadable() && dirInfo.isWritable() | |
&& !dirInfo.isFile() && !dirInfo.isSymLink()) { | |
QDir lockDirectory(lockDir); | |
fileList = dir.entryList(QStringList() << getLockPrefix() | |
+ QLatin1Char('*'), QDir::Files | QDir::Hidden | QDir::NoSymLinks); | |
foreach(const QString file, fileList) { | |
if (!lockDirectory.remove(file)) | |
_CLTHROWA(CL_ERR_IO, "Couldn't delete file "); | |
} | |
} | |
else { | |
//todo: richer error: + lockDir.getAbsolutePath()); | |
_CLTHROWA(CL_ERR_IO, "Cannot read lock directory"); | |
} | |
} | |
} | |
void FSDirectory::priv_getFN(QString& buffer, const QString& name) const | |
{ | |
buffer.clear(); | |
buffer.append(directory); | |
buffer.append(QDir::separator()); | |
buffer.append(name); | |
} | |
FSDirectory::~FSDirectory() | |
{ | |
} | |
QStringList FSDirectory::list() const | |
{ | |
CND_PRECONDITION(!directory.isEmpty(), "directory is not open"); | |
QDir dir(directory); | |
return dir.entryList(QDir::Files | QDir::Hidden); | |
} | |
bool FSDirectory::fileExists(const QString& name) const | |
{ | |
CND_PRECONDITION(!directory.isEmpty(), "directory is not open"); | |
QDir dir(directory); | |
return dir.entryList().contains(name); | |
} | |
QString FSDirectory::getDirName() const | |
{ | |
return directory; | |
} | |
//static | |
FSDirectory* FSDirectory::getDirectory(const QString& file, const bool _create) | |
{ | |
FSDirectory* dir = NULL; | |
{ | |
if (file.isEmpty()) | |
_CLTHROWA(CL_ERR_IO, "Invalid directory"); | |
SCOPED_LOCK_MUTEX(DIRECTORIES.THIS_LOCK) | |
dir = DIRECTORIES.get(file); | |
if ( dir == NULL ){ | |
dir = _CLNEW FSDirectory(file, _create); | |
DIRECTORIES.put(dir->directory, dir); | |
} else if (_create) { | |
dir->create(); | |
} | |
{ | |
SCOPED_LOCK_MUTEX(dir->THIS_LOCK) | |
dir->refCount++; | |
} | |
} | |
return _CL_POINTER(dir); | |
} | |
int64_t FSDirectory::fileModified(const QString& name) const | |
{ | |
CND_PRECONDITION(!directory.isEmpty(), "directory is not open"); | |
QFileInfo fInfo(directory + QDir::separator() + name); | |
return fInfo.lastModified().toTime_t(); | |
} | |
//static | |
int64_t FSDirectory::fileModified(const QString& dir, const QString& name) | |
{ | |
QFileInfo fInfo(dir + QDir::separator() + name); | |
return fInfo.lastModified().toTime_t(); | |
} | |
void FSDirectory::touchFile(const QString& name) | |
{ | |
CND_PRECONDITION(!directory.isEmpty(), "directory is not open"); | |
QFile file(directory + QDir::separator() + name); | |
if (!file.open(QIODevice::ReadWrite)) | |
_CLTHROWA(CL_ERR_IO, "IO Error while touching file"); | |
} | |
int64_t FSDirectory::fileLength(const QString& name) const | |
{ | |
CND_PRECONDITION(!directory.isEmpty(), "directory is not open"); | |
QFileInfo fInfo(directory + QDir::separator() + name); | |
return fInfo.size(); | |
} | |
IndexInput* FSDirectory::openInput(const QString& name) | |
{ | |
return openInput(name, CL_NS(store)::BufferedIndexOutput::BUFFER_SIZE); | |
} | |
IndexInput* FSDirectory::openInput(const QString& name, int32_t bufferSize ) | |
{ | |
CND_PRECONDITION(directory[0]!=0,"directory is not open") | |
return _CLNEW FSIndexInput(directory + QDir::separator() + name, bufferSize); | |
} | |
void FSDirectory::close() | |
{ | |
SCOPED_LOCK_MUTEX(DIRECTORIES.THIS_LOCK) | |
{ | |
SCOPED_LOCK_MUTEX(THIS_LOCK) | |
CND_PRECONDITION(!directory.isEmpty(), "directory is not open"); | |
//refcount starts at 1 | |
if (--refCount <= 0) { | |
Directory* dir = DIRECTORIES.get(getDirName()); | |
if (dir) { | |
//this will be removed in ~FSDirectory | |
DIRECTORIES.remove(getDirName()); | |
_CLDECDELETE(dir); | |
} | |
} | |
} | |
} | |
QString FSDirectory::getLockPrefix() const | |
{ | |
CND_PRECONDITION(!directory.isEmpty(), "directory is not open"); | |
QString dirName(QFileInfo(directory).absoluteFilePath()); | |
if (dirName.isEmpty()) | |
_CLTHROWA(CL_ERR_Runtime, "Invalid directory path"); | |
// to be compatible with jlucene, | |
// we need to make some changes ... | |
if (dirName.at(1) == QLatin1Char(':')) | |
dirName[0] = dirName.at(0).toUpper(); | |
TCHAR tBuffer[2048] = { 0 }; | |
dirName.toWCharArray(tBuffer); | |
char aBuffer[4096] = { 0 }; | |
STRCPY_TtoA(aBuffer, tBuffer, 4096); | |
QString string(QLatin1String("lucene-")); | |
QByteArray hash(QCryptographicHash::hash(aBuffer, QCryptographicHash::Md5)); | |
// TODO: verify this !!! | |
return string.append(QLatin1String(hash.toHex().constData())); | |
} | |
bool FSDirectory::doDeleteFile(const QString& name) | |
{ | |
CND_PRECONDITION(!directory.isEmpty(), "directory is not open"); | |
QDir dir(directory); | |
return dir.remove(name); | |
} | |
void FSDirectory::renameFile(const QString& from, const QString& to) | |
{ | |
CND_PRECONDITION(!directory.isEmpty(), "directory is not open"); | |
SCOPED_LOCK_MUTEX(THIS_LOCK) | |
if (fileExists(to)) | |
deleteFile(to, false); | |
QFile file(directory + QDir::separator() + from); | |
QString newFile(directory + QDir::separator() + to); | |
if (!file.rename(newFile)) { | |
// try a second time if we fail | |
if (fileExists(to)) | |
deleteFile(to, false); | |
if (!file.rename(newFile)) { | |
QString error(QLatin1String("Could not rename: %1 to %2!!!!")); | |
error.arg(from).arg(newFile); | |
QByteArray bArray(error.toLocal8Bit()); | |
_CLTHROWA(CL_ERR_IO, bArray.constData()); | |
} | |
} | |
} | |
IndexOutput* FSDirectory::createOutput(const QString& name) | |
{ | |
CND_PRECONDITION(!directory.isEmpty(), "directory is not open"); | |
QString file = directory + QDir::separator() + name; | |
if (QFileInfo(file).exists()) { | |
if (!QFile::remove(file)) { | |
QByteArray bArray("Cannot overwrite: "); | |
bArray.append(name.toLocal8Bit()); | |
_CLTHROWA(CL_ERR_IO, bArray.constData()); | |
} | |
} | |
return _CLNEW FSIndexOutput(file); | |
} | |
LuceneLock* FSDirectory::makeLock(const QString& name) | |
{ | |
CND_PRECONDITION(!directory.isEmpty(), "directory is not open"); | |
QString lockFile(getLockPrefix()); | |
lockFile.append(QLatin1Char('-')).append(name); | |
return _CLNEW FSLock(lockDir, lockFile); | |
} | |
QString FSDirectory::toString() const | |
{ | |
return QString::fromLatin1("FSDirectory@").append(directory); | |
} | |
CL_NS_END |