Permalink
Browse files

Read tags in worker thread.

I did some more bench marking after moving the reading to the worker thread
and found it getting 40% more operations per second when processing multiple
files. It also let us clean up some of the Baton code too.
  • Loading branch information...
1 parent 656926c commit e3c9c70707a3d67516ee93bb1917586d898d590f @drewish drewish committed May 23, 2012
Showing with 65 additions and 92 deletions.
  1. +65 −92 exiv2node.cc
View
157 exiv2node.cc
@@ -11,38 +11,29 @@
using namespace node;
using namespace v8;
+// Create a map of strings for passing them back and forth between the V8 and
+// worker threads.
+typedef std::map<std::string, std::string> tag_map_t;
+
// Base structure for passing data to and from our libuv workers.
struct Baton {
uv_work_t request;
- Exiv2::Image::AutoPtr image;
Persistent<Function> cb;
std::string fileName;
std::string exifException;
+ tag_map_t *tags;
Baton(Local<String> fn_, Handle<Function> cb_) {
uv_ref(uv_default_loop());
request.data = this;
cb = Persistent<Function>::New(cb_);
fileName = std::string(*String::AsciiValue(fn_));
exifException = std::string();
+ tags = new tag_map_t();
}
virtual ~Baton() {
uv_unref(uv_default_loop());
cb.Dispose();
- // Assuming std::auto_ptr does it's job here and Exiv2::Image::AutoPtr is
- // destroyed when it goes out of scope here..
- }
-};
-
-// For writing tags, we need to copy the strings out of the V8 types since they
-// cannot be read from the other thread.
-typedef std::map<std::string, std::string> tag_map_t;
-struct SetTagsBaton : Baton {
- tag_map_t *tags;
- SetTagsBaton(Local<String> fn_, Handle<Function> cb_) : Baton(fn_, cb_) {
- tags = new tag_map_t();
- }
- virtual ~SetTagsBaton() {
delete tags;
}
};
@@ -119,11 +110,8 @@ static Handle<Value> GetImageTags(const Arguments& args) {
if (args.Length() <= (1) || !args[1]->IsFunction())
return ThrowException(Exception::TypeError(String::New("Usage: <filename> <callback function>")));
- Local<String> fileName = Local<String>::Cast(args[0]);
- Local<Function> cb = Local<Function>::Cast(args[1]);
-
- /* Set up our thread data struct, pass off to the libuv thread pool */
- Baton *thread_data = new Baton(fileName, cb);
+ // Set up our thread data struct, pass off to the libuv thread pool.
+ Baton *thread_data = new Baton(Local<String>::Cast(args[0]), Local<Function>::Cast(args[1]));
int status = uv_queue_work(uv_default_loop(), &thread_data->request, GetImageTagsWorker, AfterGetImageTags);
assert(status == 0);
@@ -134,10 +122,34 @@ static Handle<Value> GetImageTags(const Arguments& args) {
static void GetImageTagsWorker(uv_work_t* req) {
Baton *thread_data = static_cast<Baton *> (req->data);
- /* Exiv2 processing of the file.. */
try {
- thread_data->image = Exiv2::ImageFactory::open(thread_data->fileName);
- thread_data->image->readMetadata();
+ Exiv2::Image::AutoPtr image = Exiv2::ImageFactory::open(thread_data->fileName);
+ assert(image.get() != 0);
+ image->readMetadata();
+
+ Exiv2::ExifData &exifData = image->exifData();
+ if (exifData.empty() == false) {
+ Exiv2::ExifData::const_iterator end = exifData.end();
+ for (Exiv2::ExifData::const_iterator i = exifData.begin(); i != end; ++i) {
+ thread_data->tags->insert(std::pair<std::string, std::string> (i->key(), i->value().toString()));
+ }
+ }
+
+ Exiv2::IptcData &iptcData = image->iptcData();
+ if (iptcData.empty() == false) {
+ Exiv2::IptcData::const_iterator end = iptcData.end();
+ for (Exiv2::IptcData::const_iterator i = iptcData.begin(); i != end; ++i) {
+ thread_data->tags->insert(std::pair<std::string, std::string> (i->key(), i->value().toString()));
+ }
+ }
+
+ Exiv2::XmpData &xmpData = image->xmpData();
+ if (xmpData.empty() == false) {
+ Exiv2::XmpData::const_iterator end = xmpData.end();
+ for (Exiv2::XmpData::const_iterator i = xmpData.begin(); i != end; ++i) {
+ thread_data->tags->insert(std::pair<std::string, std::string> (i->key(), i->value().toString()));
+ }
+ }
} catch (Exiv2::AnyError& e) {
thread_data->exifException.append(e.what());
}
@@ -148,48 +160,19 @@ static void AfterGetImageTags(uv_work_t* req) {
HandleScope scope;
Baton *thread_data = static_cast<Baton *> (req->data);
- Local<Value> argv[2];
+ Local<Value> argv[2] = { Local<Value>::New(Null()), Local<Value>::New(Null()) };
if (!thread_data->exifException.empty()){
argv[0] = Local<String>::New(String::New(thread_data->exifException.c_str()));
- argv[1] = Local<Value>::New(Null());
- } else {
- argv[0] = Local<Value>::New(Null());
-
- // Create a V8 object with all the image tags and their corresponding
- // values.
- Exiv2::ExifData &exifData = thread_data->image->exifData();
- Exiv2::IptcData &iptcData = thread_data->image->iptcData();
- Exiv2::XmpData &xmpData = thread_data->image->xmpData();
-
- argv[1] = Local<Value>::New(Null());
- if (exifData.empty() == false || iptcData.empty() == false || xmpData.empty() == false) {
- Local<Object> tags = Object::New();
-
- if (exifData.empty() == false) {
- Exiv2::ExifData::const_iterator end = exifData.end();
- for (Exiv2::ExifData::const_iterator i = exifData.begin(); i != end; ++i) {
- tags->Set(String::New(i->key().c_str()), String::New(i->value().toString().c_str()), ReadOnly);
- }
- }
-
- if (iptcData.empty() == false) {
- Exiv2::IptcData::const_iterator end = iptcData.end();
- for (Exiv2::IptcData::const_iterator i = iptcData.begin(); i != end; ++i) {
- tags->Set(String::New(i->key().c_str()), String::New(i->value().toString().c_str()), ReadOnly);
- }
- }
-
- if (xmpData.empty() == false) {
- Exiv2::XmpData::const_iterator end = xmpData.end();
- for (Exiv2::XmpData::const_iterator i = xmpData.begin(); i != end; ++i) {
- tags->Set(String::New(i->key().c_str()), String::New(i->value().toString().c_str()), ReadOnly);
- }
- }
- argv[1] = tags;
+ } else if (!thread_data->tags->empty()) {
+ Local<Object> tags = Object::New();
+ // Copy the tags out.
+ for (tag_map_t::iterator i = thread_data->tags->begin(); i != thread_data->tags->end(); ++i) {
+ tags->Set(String::New(i->first.c_str()), String::New(i->second.c_str()), ReadOnly);
}
+ argv[1] = tags;
}
- /* Pass the argv array object to our callback function */
+ // Pass the argv array object to our callback function.
TryCatch try_catch;
thread_data->cb->Call(Context::GetCurrent()->Global(), 2, argv);
if (try_catch.HasCaught()) {
@@ -207,13 +190,10 @@ static Handle<Value> SetImageTags(const Arguments& args) {
if (args.Length() <= 2 || !args[2]->IsFunction())
return ThrowException(Exception::TypeError(String::New("Usage: <filename> <tags> <callback function>")));
- Local<String> fileName = Local<String>::Cast(args[0]);
- Local<Object> tags = Local<Object>::Cast(args[1]);
- Local<Function> cb = Local<Function>::Cast(args[2]);
-
- /* Set up our thread data struct, pass off to the libuv thread pool */
- SetTagsBaton *thread_data = new SetTagsBaton(fileName, cb);
+ // Set up our thread data struct, pass off to the libuv thread pool.
+ Baton *thread_data = new Baton(Local<String>::Cast(args[0]), Local<Function>::Cast(args[2]));
+ Local<Object> tags = Local<Object>::Cast(args[1]);
Local<Array> keys = tags->GetPropertyNames();
for (unsigned i = 0; i < keys->Length(); i++) {
Handle<v8::Value> key = keys->Get(i);
@@ -230,22 +210,23 @@ static Handle<Value> SetImageTags(const Arguments& args) {
}
static void SetImageTagsWorker(uv_work_t *req) {
- SetTagsBaton *thread_data = static_cast<SetTagsBaton*> (req->data);
+ Baton *thread_data = static_cast<Baton*> (req->data);
- /* Read existing metadata.. TODO: also handle IPTC and XMP data here.. */
+ // TODO: also handle IPTC and XMP data here.
try {
- thread_data->image = Exiv2::ImageFactory::open(thread_data->fileName);
- thread_data->image->readMetadata();
- Exiv2::ExifData &exifData = thread_data->image->exifData();
+ Exiv2::Image::AutoPtr image = Exiv2::ImageFactory::open(thread_data->fileName);
+ assert(image.get() != 0);
+ image->readMetadata();
+ Exiv2::ExifData &exifData = image->exifData();
// Assign the tags.
for (tag_map_t::iterator i = thread_data->tags->begin(); i != thread_data->tags->end(); ++i) {
exifData[i->first].setValue(i->second);
}
- /* Write the Exif data to the image file */
- thread_data->image->setExifData(exifData);
- thread_data->image->writeMetadata();
+ // Write the Exif data to the image file.
+ image->setExifData(exifData);
+ image->writeMetadata();
} catch (Exiv2::AnyError& e) {
thread_data->exifException.append(e.what());
}
@@ -254,17 +235,15 @@ static void SetImageTagsWorker(uv_work_t *req) {
/* Thread complete callback.. */
static void AfterSetImageTags(uv_work_t *req) {
HandleScope scope;
- SetTagsBaton *thread_data = static_cast<SetTagsBaton*> (req->data);
+ Baton *thread_data = static_cast<Baton*> (req->data);
// Create an argument array for any errors.
- Local<Value> argv[1];
- if (thread_data->exifException.empty()) {
- argv[0] = Local<Value>::New(Null());
- } else {
+ Local<Value> argv[1] = { Local<Value>::New(Null()) };
+ if (!thread_data->exifException.empty()) {
argv[0] = Local<String>::New(String::New(thread_data->exifException.c_str()));
}
- /* Pass the argv array object to our callback function */
+ // Pass the argv array object to our callback function.
TryCatch try_catch;
thread_data->cb->Call(Context::GetCurrent()->Global(), 1, argv);
if (try_catch.HasCaught()) {
@@ -283,11 +262,8 @@ static Handle<Value> GetImagePreviews(const Arguments& args) {
if (args.Length() <= (1) || !args[1]->IsFunction())
return ThrowException(Exception::TypeError(String::New("Usage: <filename> <callback function>")));
- Local<String> fileName = Local<String>::Cast(args[0]);
- Local<Function> cb = Local<Function>::Cast(args[1]);
-
// Set up our thread data struct, pass off to the libuv thread pool.
- GetPreviewBaton *thread_data = new GetPreviewBaton(fileName, cb);
+ GetPreviewBaton *thread_data = new GetPreviewBaton(Local<String>::Cast(args[0]), Local<Function>::Cast(args[1]));
int status = uv_queue_work(uv_default_loop(), &thread_data->request, GetImagePreviewsWorker, AfterGetImagePreviews);
assert(status == 0);
@@ -300,11 +276,11 @@ static void GetImagePreviewsWorker(uv_work_t *req) {
GetPreviewBaton *thread_data = static_cast<GetPreviewBaton*> (req->data);
try {
- thread_data->image = Exiv2::ImageFactory::open(thread_data->fileName);
- assert(thread_data->image.get() != 0);
- thread_data->image->readMetadata();
+ Exiv2::Image::AutoPtr image = Exiv2::ImageFactory::open(thread_data->fileName);
+ assert(image.get() != 0);
+ image->readMetadata();
- Exiv2::PreviewManager manager(*thread_data->image);
+ Exiv2::PreviewManager manager(*image);
Exiv2::PreviewPropertiesList list = manager.getPreviewProperties();
// Make sure we're not reusing a baton with memory allocated.
@@ -331,10 +307,9 @@ static void AfterGetImagePreviews(uv_work_t *req) {
HandleScope scope;
GetPreviewBaton *thread_data = static_cast<GetPreviewBaton*> (req->data);
- Local<Value> argv[2];
+ Local<Value> argv[2] = { Local<Value>::New(Null()), Local<Value>::New(Null()) };
if (!thread_data->exifException.empty()){
argv[0] = Local<String>::New(String::New(thread_data->exifException.c_str()));
- argv[1] = Local<Value>::New(Null());
} else {
// Convert the data into V8 values.
Local<Array> previews = Array::New(thread_data->size);
@@ -349,8 +324,6 @@ static void AfterGetImagePreviews(uv_work_t *req) {
previews->Set(i, preview);
}
-
- argv[0] = Local<Value>::New(Null());
argv[1] = previews;
}

0 comments on commit e3c9c70

Please sign in to comment.