open image files on demand rather than keeping them open: bug #393

This commit is contained in:
brad 2004-10-29 06:30:59 +00:00
parent 134d57c220
commit 58f7d669dc
5 changed files with 128 additions and 166 deletions

View File

@ -566,7 +566,6 @@ namespace Action {
return -2;
}
int rc = image->readMetadata();
image->detach();
if (rc) {
std::cerr << path_
<< ": Could not read metadata\n";
@ -975,7 +974,6 @@ namespace {
return -2;
}
int rc = sourceImage->readMetadata();
sourceImage->detach();
if (rc) {
std::cerr << source
<< ": Could not read metadata\n";

View File

@ -72,7 +72,7 @@ namespace Exiv2 {
Caller owns the returned object and the auto-pointer ensures that
it will be deleted.
*/
Image::AutoPtr newExvInstance(const std::string& path, FILE* fp);
Image::AutoPtr newExvInstance(const std::string& path, bool create);
//! Check if the file ifp is an EXV file.
bool isExvType(FILE* ifp, bool advance);
/*!
@ -80,7 +80,7 @@ namespace Exiv2 {
Caller owns the returned object and the auto-pointer ensures that
it will be deleted.
*/
Image::AutoPtr newJpegInstance(const std::string& path, FILE* fp);
Image::AutoPtr newJpegInstance(const std::string& path, bool create);
//! Check if the file ifp is a JPEG image.
bool isJpegType(FILE* ifp, bool advance);
@ -110,35 +110,34 @@ namespace Exiv2 {
Image::Type ImageFactory::getType(const std::string& path) const
{
FILE* ifp = fopen(path.c_str(), "rb");
if (!ifp) return Image::none;
FileCloser closer(fopen(path.c_str(), "rb"));
if (!closer.fp_) return Image::none;
Image::Type type = Image::none;
Registry::const_iterator b = registry_.begin();
Registry::const_iterator e = registry_.end();
for (Registry::const_iterator i = b; i != e; ++i)
{
if (i->second.isThisType(ifp, false)) {
if (i->second.isThisType(closer.fp_, false)) {
type = i->first;
break;
}
}
fclose(ifp);
return type;
} // ImageFactory::getType
Image::AutoPtr ImageFactory::open(const std::string& path) const
{
Image::AutoPtr image;
FILE* ifp = fopen(path.c_str(), "rb");
if (!ifp) return image;
FileCloser closer(fopen(path.c_str(), "rb"));
if (!closer.fp_) return image;
Registry::const_iterator b = registry_.begin();
Registry::const_iterator e = registry_.end();
for (Registry::const_iterator i = b; i != e; ++i)
{
if (i->second.isThisType(ifp, false)) {
image = Image::AutoPtr(i->second.newInstance(path, ifp));
if (i->second.isThisType(closer.fp_, false)) {
image = Image::AutoPtr(i->second.newInstance(path, false));
break;
}
}
@ -150,10 +149,9 @@ namespace Exiv2 {
{
Registry::const_iterator i = registry_.find(type);
if (i != registry_.end()) {
return i->second.newInstance(path, 0);
return i->second.newInstance(path, true);
}
Image::AutoPtr p;
return p;
return Image::AutoPtr();
} // ImageFactory::create
@ -171,33 +169,22 @@ namespace Exiv2 {
JpegBase::JpegBase(const std::string& path, bool create,
const byte initData[], size_t dataSize)
: fp_(0), path_(path),
sizeExifData_(0), pExifData_(0), sizeIptcData_(0), pIptcData_(0)
: path_(path), sizeExifData_(0), pExifData_(0),
sizeIptcData_(0), pIptcData_(0)
{
if (create) {
fp_ = fopen(path.c_str(), "w+b");
if (fp_) initFile(initData, dataSize);
FILE* fp = fopen(path.c_str(), "w+b");
if (fp) {
initFile(fp, initData, dataSize);
fclose(fp);
}
}
else {
fp_ = fopen(path.c_str(), "rb");
}
}
JpegBase::JpegBase(const std::string& path, FILE* fp)
: fp_(fp), path_(path), sizeExifData_(0),
pExifData_(0), sizeIptcData_(0), pIptcData_(0)
{
assert(fp_);
}
int JpegBase::initFile(const byte initData[], size_t dataSize)
int JpegBase::initFile(FILE* fp, const byte initData[], size_t dataSize)
{
if (!fp_ || ferror(fp_)) return 4;
if (fwrite(initData, 1, dataSize, fp_) != dataSize) {
return 4;
}
fseek(fp_, 0, SEEK_SET);
if (ferror(fp_)) {
if (!fp || ferror(fp)) return 4;
if (fwrite(initData, 1, dataSize, fp) != dataSize) {
return 4;
}
return 0;
@ -205,23 +192,15 @@ namespace Exiv2 {
JpegBase::~JpegBase()
{
if (fp_) fclose(fp_);
delete[] pExifData_;
delete[] pIptcData_;
}
int JpegBase::detach()
{
if (fp_) fclose(fp_);
fp_ = 0;
return 0;
}
bool JpegBase::good() const
{
if (fp_ == 0) return false;
rewind(fp_);
return isThisType(fp_, false);
FileCloser closer(fopen(path_.c_str(), "rb"));
if (closer.fp_ == 0 ) return false;
return isThisType(closer.fp_, false);
}
void JpegBase::clearMetadata()
@ -282,16 +261,16 @@ namespace Exiv2 {
setComment(image.comment());
}
int JpegBase::advanceToMarker() const
int JpegBase::advanceToMarker(FILE *fp) const
{
int c = -1;
// Skips potential padding between markers
while ((c=fgetc(fp_)) != 0xff) {
while ((c=fgetc(fp)) != 0xff) {
if (c == EOF) return -1;
}
// Markers can start with any number of 0xff
while ((c=fgetc(fp_)) == 0xff) {
while ((c=fgetc(fp)) == 0xff) {
if (c == EOF) return -1;
}
return c;
@ -299,12 +278,12 @@ namespace Exiv2 {
int JpegBase::readMetadata()
{
if (!fp_) return 1;
rewind(fp_);
FileCloser closer(fopen(path_.c_str(), "rb"));
if (!closer.fp_) return 1;
// Ensure that this is the correct image type
if (!isThisType(fp_, true)) {
if (ferror(fp_) || feof(fp_)) return 1;
if (!isThisType(closer.fp_, true)) {
if (ferror(closer.fp_) || feof(closer.fp_)) return 1;
return 2;
}
clearMetadata();
@ -314,23 +293,23 @@ namespace Exiv2 {
DataBuf buf(bufMinSize);
// Read section marker
int marker = advanceToMarker();
int marker = advanceToMarker(closer.fp_);
if (marker < 0) return 2;
while (marker != sos_ && marker != eoi_ && search > 0) {
// Read size and signature (ok if this hits EOF)
bufRead = (long)fread(buf.pData_, 1, bufMinSize, fp_);
if (ferror(fp_)) return 1;
bufRead = (long)fread(buf.pData_, 1, bufMinSize, closer.fp_);
if (ferror(closer.fp_)) return 1;
uint16_t size = getUShort(buf.pData_, bigEndian);
if (marker == app1_ && memcmp(buf.pData_ + 2, exifId_, 6) == 0) {
if (size < 8) return 2;
// Seek to begining and read the Exif data
fseek(fp_, 8-bufRead, SEEK_CUR);
fseek(closer.fp_, 8-bufRead, SEEK_CUR);
long sizeExifData = size - 8;
pExifData_ = new byte[sizeExifData];
fread(pExifData_, 1, sizeExifData, fp_);
if (ferror(fp_) || feof(fp_)) {
fread(pExifData_, 1, sizeExifData, closer.fp_);
if (ferror(closer.fp_) || feof(closer.fp_)) {
delete[] pExifData_;
pExifData_ = 0;
return 1;
@ -342,10 +321,10 @@ namespace Exiv2 {
else if (marker == app13_ && memcmp(buf.pData_ + 2, ps3Id_, 14) == 0) {
if (size < 16) return 2;
// Read the rest of the APP13 segment
// needed if bufMinSize!=16: fseek(fp_, 16-bufRead, SEEK_CUR);
// needed if bufMinSize!=16: fseek(closer.fp_, 16-bufRead, SEEK_CUR);
DataBuf psData(size - 16);
fread(psData.pData_, 1, psData.size_, fp_);
if (ferror(fp_) || feof(fp_)) return 1;
fread(psData.pData_, 1, psData.size_, closer.fp_);
if (ferror(closer.fp_) || feof(closer.fp_)) return 1;
const byte *record = 0;
uint16_t sizeIptc = 0;
uint16_t sizeHdr = 0;
@ -365,10 +344,10 @@ namespace Exiv2 {
// Jpegs can have multiple comments, but for now only read
// the first one (most jpegs only have one anyway). Comments
// are simple single byte ISO-8859-1 strings.
fseek(fp_, 2-bufRead, SEEK_CUR);
fseek(closer.fp_, 2-bufRead, SEEK_CUR);
buf.alloc(size-2);
fread(buf.pData_, 1, size-2, fp_);
if (ferror(fp_) || feof(fp_)) return 1;
fread(buf.pData_, 1, size-2, closer.fp_);
if (ferror(closer.fp_) || feof(closer.fp_)) return 1;
comment_.assign(reinterpret_cast<char*>(buf.pData_), size-2);
while ( comment_.length()
&& comment_.at(comment_.length()-1) == '\0') {
@ -379,10 +358,10 @@ namespace Exiv2 {
else {
if (size < 2) return 2;
// Skip the remainder of the unknown segment
if (fseek(fp_, size-bufRead, SEEK_CUR)) return 2;
if (fseek(closer.fp_, size-bufRead, SEEK_CUR)) return 2;
}
// Read the beginning of the next segment
marker = advanceToMarker();
marker = advanceToMarker(closer.fp_);
if (marker < 0) return 2;
}
return 0;
@ -434,18 +413,18 @@ namespace Exiv2 {
int JpegBase::writeMetadata()
{
if (!fp_) return 1;
rewind(fp_);
FileCloser reader(fopen(path_.c_str(), "rb"));
if (!reader.fp_) return 1;
// Write the output to a temporary file
pid_t pid = getpid();
std::string tmpname = path_ + toString(pid);
FILE* ofl = fopen(tmpname.c_str(), "wb");
if (!ofl) return -3;
FileCloser writer(fopen(tmpname.c_str(), "wb"));
if (!writer.fp_) return -3;
int rc = doWriteMetadata(ofl);
fclose(ofl);
fclose(fp_);
int rc = doWriteMetadata(reader.fp_, writer.fp_);
writer.close();
reader.close();
if (rc == 0) {
// Workaround for MSVCRT rename that does not overwrite existing files
if (remove(path_.c_str()) != 0) rc = -4;
@ -458,26 +437,24 @@ namespace Exiv2 {
// remove temporary file
remove(tmpname.c_str());
}
// Reopen the file
fp_ = fopen(path_.c_str(), "rb");
if (!fp_) return -1;
return rc;
} // JpegBase::writeMetadata
int JpegBase::doWriteMetadata(FILE* ofp) const
int JpegBase::doWriteMetadata(FILE *ifp, FILE* ofp) const
{
if (!fp_) return 1;
if (!ifp) return 1;
if (!ofp) return 4;
// Ensure that this is the correct image type
if (!isThisType(fp_, true)) {
if (ferror(fp_) || feof(fp_)) return 1;
if (!isThisType(ifp, true)) {
if (ferror(ifp) || feof(ifp)) return 1;
return 2;
}
const long bufMinSize = 16;
long bufRead = 0;
DataBuf buf(bufMinSize);
const long seek = ftell(fp_);
const long seek = ftell(ifp);
int count = 0;
int search = 0;
int insertPos = 0;
@ -490,7 +467,7 @@ namespace Exiv2 {
if (writeHeader(ofp)) return 4;
// Read section marker
int marker = advanceToMarker();
int marker = advanceToMarker(ifp);
if (marker < 0) return 2;
// First find segments of interest. Normally app0 is first and we want
@ -498,45 +475,44 @@ namespace Exiv2 {
// don't bother.
while (marker != sos_ && marker != eoi_ && search < 3) {
// Read size and signature (ok if this hits EOF)
bufRead = (long)fread(buf.pData_, 1, bufMinSize, fp_);
if (ferror(fp_)) return 1;
bufRead = (long)fread(buf.pData_, 1, bufMinSize, ifp);
if (ferror(ifp)) return 1;
uint16_t size = getUShort(buf.pData_, bigEndian);
if (marker == app0_) {
if (size < 2) return 2;
insertPos = count + 1;
if (fseek(fp_, size-bufRead, SEEK_CUR)) return 2;
if (fseek(ifp, size-bufRead, SEEK_CUR)) return 2;
}
else if (marker == app1_ && memcmp(buf.pData_ + 2, exifId_, 6) == 0) {
if (size < 8) return 2;
skipApp1Exif = count;
++search;
if (fseek(fp_, size-bufRead, SEEK_CUR)) return 2;
if (fseek(ifp, size-bufRead, SEEK_CUR)) return 2;
}
else if (marker == app13_ && memcmp(buf.pData_ + 2, ps3Id_, 14) == 0) {
if (size < 16) return 2;
skipApp13Ps3 = count;
++search;
// needed if bufMinSize!=16: fseek(fp_, 16-bufRead, SEEK_CUR);
// needed if bufMinSize!=16: fseek(ifp, 16-bufRead, SEEK_CUR);
psData.alloc(size - 16);
// Load PS data now to allow reinsertion at any point
fread(psData.pData_, 1, psData.size_, fp_);
if (ferror(fp_) || feof(fp_)) return 1;
fread(psData.pData_, 1, psData.size_, ifp);
if (ferror(ifp) || feof(ifp)) return 1;
}
else if (marker == com_ && skipCom == -1)
{
else if (marker == com_ && skipCom == -1) {
if (size < 2) return 2;
// Jpegs can have multiple comments, but for now only handle
// the first one (most jpegs only have one anyway).
skipCom = count;
++search;
if (fseek(fp_, size-bufRead, SEEK_CUR)) return 2;
if (fseek(ifp, size-bufRead, SEEK_CUR)) return 2;
}
else {
if (size < 2) return 2;
if (fseek(fp_, size-bufRead, SEEK_CUR)) return 2;
if (fseek(ifp, size-bufRead, SEEK_CUR)) return 2;
}
marker = advanceToMarker();
marker = advanceToMarker(ifp);
if (marker < 0) return 2;
++count;
}
@ -545,9 +521,9 @@ namespace Exiv2 {
if (pIptcData_) ++search;
if (!comment_.empty()) ++search;
fseek(fp_, seek, SEEK_SET);
fseek(ifp, seek, SEEK_SET);
count = 0;
marker = advanceToMarker();
marker = advanceToMarker(ifp);
if (marker < 0) return 2;
// To simplify this a bit, new segments are inserts at either the start
@ -556,8 +532,8 @@ namespace Exiv2 {
// Segments are erased if there is no assigned metadata.
while (marker != sos_ && search > 0) {
// Read size and signature (ok if this hits EOF)
bufRead = (long)fread(buf.pData_, 1, bufMinSize, fp_);
if (ferror(fp_)) return 1;
bufRead = (long)fread(buf.pData_, 1, bufMinSize, ifp);
if (ferror(ifp)) return 1;
// Careful, this can be a meaningless number for empty
// images with only an eoi_ marker
uint16_t size = getUShort(buf.pData_, bigEndian);
@ -624,7 +600,7 @@ namespace Exiv2 {
if (fwrite(tmpBuf, 1, 12, ofp) != 12) return 4;
if (fwrite(pIptcData_, 1, sizeIptcData_ , ofp) != (size_t)sizeIptcData_) return 4;
// data is padded to be even (but not included in size)
if (sizeIptcData_ & 1 ) {
if (sizeIptcData_ & 1) {
if (fputc(0, ofp)==EOF) return 4;
}
if (ferror(ofp)) return 4;
@ -636,35 +612,35 @@ namespace Exiv2 {
if (ferror(ofp)) return 4;
}
}
if( marker == eoi_ ) {
if (marker == eoi_) {
break;
}
else if (skipApp1Exif==count || skipApp13Ps3==count || skipCom==count) {
--search;
fseek(fp_, size-bufRead, SEEK_CUR);
fseek(ifp, size-bufRead, SEEK_CUR);
}
else {
if (size < 2) return 2;
buf.alloc(size+2);
fseek(fp_, -bufRead-2, SEEK_CUR);
fread(buf.pData_, 1, size+2, fp_);
if (ferror(fp_) || feof(fp_)) return 1;
fseek(ifp, -bufRead-2, SEEK_CUR);
fread(buf.pData_, 1, size+2, ifp);
if (ferror(ifp) || feof(ifp)) return 1;
if (fwrite(buf.pData_, 1, size+2, ofp) != (size_t)size+2) return 4;
if (ferror(ofp)) return 4;
}
// Next marker
marker = advanceToMarker();
marker = advanceToMarker(ifp);
if (marker < 0) return 2;
++count;
}
// Copy rest of the stream
fseek(fp_, -2, SEEK_CUR);
fseek(ifp, -2, SEEK_CUR);
fflush( ofp );
buf.alloc(4096);
size_t readSize = 0;
while ((readSize=fread(buf.pData_, 1, buf.size_, fp_))) {
while ((readSize=fread(buf.pData_, 1, buf.size_, ifp))) {
if (fwrite(buf.pData_, 1, readSize, ofp) != readSize) return 4;
}
if (ferror(ofp)) return 4;
@ -713,16 +689,16 @@ namespace Exiv2 {
return isJpegType(ifp, advance);
}
Image::AutoPtr newJpegInstance(const std::string& path, FILE* fp)
Image::AutoPtr newJpegInstance(const std::string& path, bool create)
{
Image::AutoPtr image;
if (fp == 0) {
if (create) {
image = Image::AutoPtr(new JpegImage(path, true));
if (!image->good()) image.reset();
}
else {
image = Image::AutoPtr(new JpegImage(path, fp));
image = Image::AutoPtr(new JpegImage(path, false));
}
if (!image->good()) image.reset();
return image;
}
@ -766,16 +742,16 @@ namespace Exiv2 {
return isExvType(ifp, advance);
}
Image::AutoPtr newExvInstance(const std::string& path, FILE* fp)
Image::AutoPtr newExvInstance(const std::string& path, bool create)
{
Image::AutoPtr image;
if (fp == 0) {
if (create) {
image = Image::AutoPtr(new ExvImage(path, true));
if (!image->good()) image.reset();
}
else {
image = Image::AutoPtr(new ExvImage(path, fp));
image = Image::AutoPtr(new ExvImage(path, false));
}
if (!image->good()) image.reset();
return image;
}

View File

@ -126,11 +126,6 @@ namespace Exiv2 {
from the actual file until writeMetadata is called.
*/
virtual void clearMetadata() =0;
/*!
@brief Close associated image file but preserve buffered metadata.
@return 0 if successful.
*/
virtual int detach() =0;
//@}
//! @name Accessors
@ -179,7 +174,7 @@ namespace Exiv2 {
//! Type for function pointer that creates new Image instances
typedef Image::AutoPtr (*NewInstanceFct)(const std::string& path,
FILE* ifp);
bool create);
//! Type for function pointer that checks image types
typedef bool (*IsThisTypeFct)(FILE* ifp, bool advance);
@ -325,7 +320,6 @@ namespace Exiv2 {
void clearComment();
void setMetadata(const Image& image);
void clearMetadata();
int detach();
//@}
//! @name Accessors
@ -341,13 +335,6 @@ namespace Exiv2 {
protected:
//! @name Creators
//@{
/*!
@brief Constructor for subclasses that have already opened a
file stream on the specified path.
@param path Full path to image file.
@param fp File pointer to open file.
*/
JpegBase(const std::string& path, FILE* fp);
/*!
@brief Constructor that can either open an existing image or create
a new image from scratch. If a new image is to be created, any
@ -409,7 +396,6 @@ namespace Exiv2 {
private:
// DATA
FILE* fp_; //!< Image file (read write)
const std::string path_; //!< Image file name
long sizeExifData_; //!< Size of the Exif data buffer
byte* pExifData_; //!< Exif data buffer
@ -422,10 +408,11 @@ namespace Exiv2 {
@brief Advances file stream to one byte past the next Jpeg marker
and returns the marker. This method should be called when the
file stream is positioned one byte past the end of a Jpeg segment.
@param fp File stream to advance
@return the next Jpeg segment marker if successful;<BR>
-1 if a maker was not found before EOF;<BR>
*/
int advanceToMarker() const;
int advanceToMarker(FILE *fp) const;
/*!
@brief Locates Photoshop formated Iptc data in a memory buffer.
Operates on raw data (rather than file streams) to simplify reuse.
@ -450,23 +437,25 @@ namespace Exiv2 {
uint16_t *const sizeHdr,
uint16_t *const sizeIptc) const;
/*!
@brief Write to the associated file stream with the provided data.
@brief Write to the specified file stream with the provided data.
@param fp File stream to be written to (should be "w+b" mode)
@param initData Data to be written to the associated file
@param dataSize Size in bytes of data to be written
@return 0 if successful;<BR>
4 if the output file can not be written to;<BR>
*/
int initFile(const byte initData[], size_t dataSize);
int initFile(FILE* fp, const byte initData[], size_t dataSize);
/*!
@brief Provides the main implementation of writeMetadata by
writing all buffered metadata to associated file.
@param os Output stream to write to (e.g., a temporary file).
@param ifp Input file stream. Non-metadata is copied to output file.
@param ofp Output file stream to write to (e.g., a temporary file).
@return 0 if successful;<br>
1 if reading from associated file failed;<BR>
2 if the file does not contain a valid image;<BR>
4 if the temporary output file can not be written to;<BR>
1 if reading from input file failed;<BR>
2 if the input file does not contain a valid image;<BR>
4 if the output file can not be written to;<BR>
*/
int doWriteMetadata(FILE* ofp) const;
int doWriteMetadata(FILE *ifp, FILE* ofp) const;
// NOT Implemented
//! Default constructor.
@ -481,7 +470,6 @@ namespace Exiv2 {
@brief Helper class to access JPEG images
*/
class JpegImage : public JpegBase {
friend Image::AutoPtr newJpegInstance(const std::string& path, FILE* fp);
friend bool isJpegType(FILE* ifp, bool advance);
public:
//! @name Creators
@ -528,17 +516,6 @@ namespace Exiv2 {
static const byte soi_; // SOI marker
static const byte blank_[]; // Minimal Jpeg image
//! @name Creators
//@{
/*!
@brief Constructor to be used when a Jpeg file has already
been opened. Meant for internal factory use.
@param path Full path to opened image file.
@param fp File pointer to open file.
*/
JpegImage(const std::string& path, FILE* fp) : JpegBase(path, fp) {}
//@}
// NOT Implemented
//! Default constructor
JpegImage();
@ -550,7 +527,6 @@ namespace Exiv2 {
//! Helper class to access %Exiv2 files
class ExvImage : public JpegBase {
friend Image::AutoPtr newExvInstance(const std::string& path, FILE* fp);
friend bool isExvType(FILE* ifp, bool advance);
public:
//! @name Creators
@ -597,17 +573,6 @@ namespace Exiv2 {
static const char exiv2Id_[]; // Exv identifier
static const byte blank_[]; // Minimal exiv file
//! @name Creators
//@{
/*!
@brief Constructor to be used when an Exv file has already
been opened. Meant for internal factory use.
@param path Full path to opened image file.
@param fp File pointer to open file.
*/
ExvImage(const std::string& path, FILE* fp) : JpegBase(path, fp) {}
//@}
// NOT Implemented
//! Default constructor
ExvImage();

View File

@ -62,7 +62,6 @@ try {
": Could not read metadata from (" << params.read_ << ")\n";
return 5;
}
readImg->detach();
Exiv2::Image::AutoPtr writeImg
= Exiv2::ImageFactory::instance().open(params.write_);

View File

@ -153,6 +153,30 @@ namespace Exiv2 {
byte* pData_;
}; // class DataBuf
/*!
@brief Utility class that closes a file stream pointer upon destruction.
Its primary use is to be a stack variable in functions that need
to ensure files get closed. Useful when functions return errors
from many locations.
*/
class FileCloser {
// Not implemented
//! Copy constructor
FileCloser(const FileCloser&);
//! Assignment operator
FileCloser& operator=(const FileCloser&);
public:
//! Default constructor
FileCloser() : fp_(0) {}
//! Constructor with an initial buffer size
FileCloser(FILE *fp) : fp_(fp) {}
//! Destructor, deletes the allocated buffer
~FileCloser() { close(); }
void close() { if (fp_) fclose(fp_); fp_ = 0; }
//! The file stream pointer
FILE *fp_;
}; // class FileCloser
// *****************************************************************************
// free functions