<commit_before>// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chrome/browser/extensions/image_loading_tracker.h"

#include <string>
#include <vector>

#include "base/bind.h"
#include "base/file_util.h"
#include "chrome/browser/ui/webui/extensions/extension_icon_source.h"
#include "chrome/common/chrome_notification_types.h"
#include "chrome/common/extensions/extension.h"
#include "chrome/common/extensions/extension_constants.h"
#include "chrome/common/extensions/extension_resource.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/notification_service.h"
#include "grit/component_extension_resources_map.h"
#include "grit/theme_resources.h"
#include "skia/ext/image_operations.h"
#include "third_party/skia/include/core/SkBitmap.h"
#include "ui/base/resource/resource_bundle.h"
#include "ui/gfx/image/image.h"
#include "ui/gfx/image/image_skia_rep.h"
#include "webkit/glue/image_decoder.h"

using content::BrowserThread;
using extensions::Extension;

namespace {

struct ComponentExtensionResource {
  const char* extension_id;
  const int resource_id;
};

const ComponentExtensionResource kSpecialComponentExtensionResources[] = {
  { extension_misc::kWebStoreAppId, IDR_WEBSTORE_ICON },
  { extension_misc::kChromeAppId, IDR_PRODUCT_LOGO_128 },
};

// Finds special component extension resource id for given extension id.
bool FindSpecialExtensionResourceId(const std::string& extension_id,
                                    int* out_resource_id) {
  for (size_t i = 0; i < arraysize(kSpecialComponentExtensionResources); ++i) {
    if (extension_id == kSpecialComponentExtensionResources[i].extension_id) {
      if (out_resource_id)
        *out_resource_id = kSpecialComponentExtensionResources[i].resource_id;
      return true;
    }
  }

  return false;
}

bool ShouldResizeImageRepresentation(
    ImageLoadingTracker::ImageRepresentation::ResizeCondition resize_method,
    const gfx::Size& decoded_size,
    const gfx::Size& desired_size) {
  switch (resize_method) {
    case ImageLoadingTracker::ImageRepresentation::ALWAYS_RESIZE:
      return decoded_size != desired_size;
    case ImageLoadingTracker::ImageRepresentation::RESIZE_WHEN_LARGER:
      return decoded_size.width() > desired_size.width() ||
             decoded_size.height() > desired_size.height();
    default:
      NOTREACHED();
      return false;
  }
}

}  // namespace

////////////////////////////////////////////////////////////////////////////////
// ImageLoadingTracker::Observer

ImageLoadingTracker::Observer::~Observer() {}

////////////////////////////////////////////////////////////////////////////////
// ImageLoadingTracker::ImageRepresentation

ImageLoadingTracker::ImageRepresentation::ImageRepresentation(
    const ExtensionResource& resource,
    ResizeCondition resize_method,
    const gfx::Size& desired_size,
    ui::ScaleFactor scale_factor)
    : resource(resource),
      resize_method(resize_method),
      desired_size(desired_size),
      scale_factor(scale_factor) {
}

ImageLoadingTracker::ImageRepresentation::~ImageRepresentation() {
}

////////////////////////////////////////////////////////////////////////////////
// ImageLoadingTracker::PendingLoadInfo

ImageLoadingTracker::PendingLoadInfo::PendingLoadInfo()
  : extension(NULL),
    cache(CACHE),
    pending_count(0) {
}

ImageLoadingTracker::PendingLoadInfo::~PendingLoadInfo() {}

////////////////////////////////////////////////////////////////////////////////
// ImageLoadingTracker::ImageLoader

// A RefCounted class for loading bitmaps/image reps on the File thread and
// reporting back on the UI thread.
class ImageLoadingTracker::ImageLoader
    : public base::RefCountedThreadSafe<ImageLoader> {
 public:
  explicit ImageLoader(ImageLoadingTracker* tracker)
      : tracker_(tracker) {
    CHECK(BrowserThread::GetCurrentThreadIdentifier(&callback_thread_id_));
    DCHECK(!BrowserThread::CurrentlyOn(BrowserThread::FILE));
  }

  // Lets this class know that the tracker is no longer interested in the
  // results.
  void StopTracking() {
    tracker_ = NULL;
  }

  // Instructs the loader to load a task on the File thread.
  void LoadImage(const ImageRepresentation& image_info, int id) {
    DCHECK(BrowserThread::CurrentlyOn(callback_thread_id_));
    BrowserThread::PostTask(
        BrowserThread::FILE, FROM_HERE,
        base::Bind(&ImageLoader::LoadOnFileThread, this, image_info, id));
  }

  void LoadOnFileThread(const ImageRepresentation& image_info, int id) {
    DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));

    // Read the file from disk.
    std::string file_contents;
    FilePath path = image_info.resource.GetFilePath();
    if (path.empty() || !file_util::ReadFileToString(path, &file_contents)) {
      ReportBack(NULL, image_info, gfx::Size(), id);
      return;
    }

    // Decode the bitmap using WebKit's image decoder.
    const unsigned char* data =
        reinterpret_cast<const unsigned char*>(file_contents.data());
    webkit_glue::ImageDecoder decoder;
    scoped_ptr<SkBitmap> decoded(new SkBitmap());
    // Note: This class only decodes bitmaps from extension resources. Chrome
    // doesn't (for security reasons) directly load extension resources provided
    // by the extension author, but instead decodes them in a separate
    // locked-down utility process. Only if the decoding succeeds is the image
    // saved from memory to disk and subsequently used in the Chrome UI.
    // Chrome is therefore decoding bitmaps here that were generated by Chrome.
    *decoded = decoder.Decode(data, file_contents.length());
    if (decoded->empty()) {
      ReportBack(NULL, image_info, gfx::Size(), id);
      return;  // Unable to decode.
    }

    gfx::Size original_size(decoded->width(), decoded->height());
    *decoded = ResizeIfNeeded(*decoded, image_info);

    ReportBack(decoded.release(), image_info, original_size, id);
  }

  // Instructs the loader to load a resource on the File thread.
  void LoadResource(const ImageRepresentation& image_info,
                    int id,
                    int resource_id) {
    DCHECK(BrowserThread::CurrentlyOn(callback_thread_id_));
    BrowserThread::PostTask(
        BrowserThread::FILE, FROM_HERE,
        base::Bind(&ImageLoader::LoadResourceOnFileThread, this, image_info,
                   id, resource_id));
  }

  void LoadResourceOnFileThread(const ImageRepresentation& image_info,
                                int id,
                                int resource_id) {
    DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
    // TODO(xiyuan): Clean up to use SkBitmap here and in LoadOnFileThread.
    scoped_ptr<SkBitmap> bitmap(new SkBitmap);
    *bitmap = ResourceBundle::GetSharedInstance().GetImageNamed(
        resource_id).AsBitmap();

    *bitmap = ResizeIfNeeded(*bitmap, image_info);
    ReportBack(bitmap.release(), image_info, image_info.desired_size, id);
  }

  void ReportBack(const SkBitmap* bitmap, const ImageRepresentation& image_info,
                  const gfx::Size& original_size, int id) {
    DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));

    BrowserThread::PostTask(
        callback_thread_id_, FROM_HERE,
        base::Bind(&ImageLoader::ReportOnCallingThread, this,
                   bitmap, image_info, original_size, id));
  }

  void ReportOnCallingThread(const SkBitmap* bitmap,
                             const ImageRepresentation& image_info,
                             const gfx::Size& original_size,
                             int id) {
    DCHECK(BrowserThread::CurrentlyOn(callback_thread_id_));

    if (tracker_)
      tracker_->OnBitmapLoaded(bitmap, image_info, original_size, id, true);

    if (bitmap)
      delete bitmap;
  }

 private:
  friend class base::RefCountedThreadSafe<ImageLoader>;
  ~ImageLoader() {}

  SkBitmap ResizeIfNeeded(const SkBitmap& bitmap,
                          const ImageRepresentation& image_info) {
    gfx::Size original_size(bitmap.width(), bitmap.height());
    if (ShouldResizeImageRepresentation(image_info.resize_method,
                                        original_size,
                                        image_info.desired_size)) {
      return skia::ImageOperations::Resize(
          bitmap, skia::ImageOperations::RESIZE_LANCZOS3,
          image_info.desired_size.width(), image_info.desired_size.height());
    }

    return bitmap;
  }

  // The tracker we are loading the bitmap for. If NULL, it means the tracker is
  // no longer interested in the reply.
  ImageLoadingTracker* tracker_;

  // The thread that we need to call back on to report that we are done.
  BrowserThread::ID callback_thread_id_;

  DISALLOW_COPY_AND_ASSIGN(ImageLoader);
};

////////////////////////////////////////////////////////////////////////////////
// ImageLoadingTracker

// static
bool ImageLoadingTracker::IsSpecialBundledExtensionId(
    const std::string& extension_id) {
  int resource_id = -1;
  return FindSpecialExtensionResourceId(extension_id, &resource_id);
}

ImageLoadingTracker::ImageLoadingTracker(Observer* observer)
    : observer_(observer),
      next_id_(0) {
  registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_UNLOADED,
                 content::NotificationService::AllSources());
}

ImageLoadingTracker::~ImageLoadingTracker() {
  // The loader is created lazily and is NULL if the tracker is destroyed before
  // any valid image load tasks have been posted.
  if (loader_)
    loader_->StopTracking();
}

void ImageLoadingTracker::LoadImage(const Extension* extension,
                                    const ExtensionResource& resource,
                                    const gfx::Size& max_size,
                                    CacheParam cache) {
  std::vector<ImageRepresentation> info_list;
  info_list.push_back(ImageRepresentation(
      resource,
      ImageRepresentation::RESIZE_WHEN_LARGER,
      max_size,
      ui::SCALE_FACTOR_100P));
  LoadImages(extension, info_list, cache);
}

void ImageLoadingTracker::LoadImages(
    const Extension* extension,
    const std::vector<ImageRepresentation>& info_list,
    CacheParam cache) {
  PendingLoadInfo load_info;
  load_info.extension = extension;
  load_info.cache = cache;
  load_info.extension_id = extension->id();
  load_info.pending_count = info_list.size();
  int id = next_id_++;
  load_map_[id] = load_info;

  for (std::vector<ImageRepresentation>::const_iterator it = info_list.begin();
       it != info_list.end(); ++it) {
    int resource_id = -1;

    // Load resources for special component extensions.
    if (FindSpecialExtensionResourceId(load_info.extension_id, &resource_id)) {
      if (!loader_)
        loader_ = new ImageLoader(this);
      loader_->LoadResource(*it, id, resource_id);
      continue;
    }

    // If we don't have a path we don't need to do any further work, just
    // respond back.
    if (it->resource.relative_path().empty()) {
      OnBitmapLoaded(NULL, *it, it->desired_size, id, false);
      continue;
    }

    DCHECK(extension->path() == it->resource.extension_root());

    // See if the extension has the bitmap already.
    if (extension->HasCachedImage(it->resource, it->desired_size)) {
      SkBitmap bitmap = extension->GetCachedImage(it->resource,
                                                  it->desired_size);
      OnBitmapLoaded(&bitmap, *it, it->desired_size, id, false);
      continue;
    }

    // Instruct the ImageLoader to load this on the File thread. LoadImage and
    // LoadResource do not block.
    if (!loader_)
      loader_ = new ImageLoader(this);

    if (IsComponentExtensionResource(extension, it->resource, resource_id))
      loader_->LoadResource(*it, id, resource_id);
    else
      loader_->LoadImage(*it, id);
  }
}

bool ImageLoadingTracker::IsComponentExtensionResource(
    const Extension* extension,
    const ExtensionResource& resource,
    int& resource_id) const {
  if (extension->location() != Extension::COMPONENT)
    return false;

  FilePath directory_path = extension->path();
  FilePath relative_path = directory_path.BaseName().Append(
      resource.relative_path());

  for (size_t i = 0; i < kComponentExtensionResourcesSize; ++i) {
    FilePath resource_path =
        FilePath().AppendASCII(kComponentExtensionResources[i].name);
    resource_path = resource_path.NormalizePathSeparators();

    if (relative_path == resource_path) {
      resource_id = kComponentExtensionResources[i].value;
      return true;
    }
  }
  return false;
}

void ImageLoadingTracker::OnBitmapLoaded(
    const SkBitmap* bitmap,
    const ImageRepresentation& image_info,
    const gfx::Size& original_size,
    int id,
    bool should_cache) {
  LoadMap::iterator load_map_it = load_map_.find(id);
  DCHECK(load_map_it != load_map_.end());
  PendingLoadInfo* info = &load_map_it->second;

  // Save the pending results.
  DCHECK_GT(info->pending_count, 0u);
  info->pending_count--;
  if (bitmap) {
    info->image_skia.AddRepresentation(gfx::ImageSkiaRep(*bitmap,
                                       image_info.scale_factor));
  }

  // Add to the extension's bitmap cache if requested.
  DCHECK(info->cache != CACHE || info->extension);
  if (should_cache && info->cache == CACHE  && !image_info.resource.empty() &&
      !info->extension->HasCachedImage(image_info.resource, original_size)) {
    info->extension->SetCachedImage(image_info.resource,
                                    bitmap ? *bitmap : SkBitmap(),
                                    original_size);
  }

  // If all pending bitmaps are done then report back.
  if (info->pending_count == 0) {
    gfx::Image image;
    std::string extension_id = info->extension_id;

    if (!info->image_skia.isNull()) {
      info->image_skia.MakeThreadSafe();
      image = gfx::Image(info->image_skia);
    }

    load_map_.erase(load_map_it);

    // ImageLoadingTracker might be deleted after the callback so don't do
    // anything after this statement.
    observer_->OnImageLoaded(image, extension_id, id);
  }
}

void ImageLoadingTracker::Observe(int type,
                                  const content::NotificationSource& source,
                                  const content::NotificationDetails& details) {
  DCHECK(type == chrome::NOTIFICATION_EXTENSION_UNLOADED);

  const Extension* extension =
      content::Details<extensions::UnloadedExtensionInfo>(details)->extension;

  // Remove reference to this extension from all pending load entries. This
  // ensures we don't attempt to cache the bitmap when the load completes.
  for (LoadMap::iterator i = load_map_.begin(); i != load_map_.end(); ++i) {
    PendingLoadInfo* info = &i->second;
    if (info->extension == extension) {
      info->extension = NULL;
      info->cache = DONT_CACHE;
    }
  }
}
<commit_msg>Use blocking pool instead of file thread for ILT.<commit_after>// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chrome/browser/extensions/image_loading_tracker.h"

#include <string>
#include <vector>

#include "base/bind.h"
#include "base/file_util.h"
#include "base/threading/sequenced_worker_pool.h"
#include "chrome/browser/ui/webui/extensions/extension_icon_source.h"
#include "chrome/common/chrome_notification_types.h"
#include "chrome/common/extensions/extension.h"
#include "chrome/common/extensions/extension_constants.h"
#include "chrome/common/extensions/extension_resource.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/notification_service.h"
#include "grit/component_extension_resources_map.h"
#include "grit/theme_resources.h"
#include "skia/ext/image_operations.h"
#include "third_party/skia/include/core/SkBitmap.h"
#include "ui/base/resource/resource_bundle.h"
#include "ui/gfx/image/image.h"
#include "ui/gfx/image/image_skia_rep.h"
#include "webkit/glue/image_decoder.h"

using content::BrowserThread;
using extensions::Extension;

namespace {

struct ComponentExtensionResource {
  const char* extension_id;
  const int resource_id;
};

const ComponentExtensionResource kSpecialComponentExtensionResources[] = {
  { extension_misc::kWebStoreAppId, IDR_WEBSTORE_ICON },
  { extension_misc::kChromeAppId, IDR_PRODUCT_LOGO_128 },
};

// Finds special component extension resource id for given extension id.
bool FindSpecialExtensionResourceId(const std::string& extension_id,
                                    int* out_resource_id) {
  for (size_t i = 0; i < arraysize(kSpecialComponentExtensionResources); ++i) {
    if (extension_id == kSpecialComponentExtensionResources[i].extension_id) {
      if (out_resource_id)
        *out_resource_id = kSpecialComponentExtensionResources[i].resource_id;
      return true;
    }
  }

  return false;
}

bool ShouldResizeImageRepresentation(
    ImageLoadingTracker::ImageRepresentation::ResizeCondition resize_method,
    const gfx::Size& decoded_size,
    const gfx::Size& desired_size) {
  switch (resize_method) {
    case ImageLoadingTracker::ImageRepresentation::ALWAYS_RESIZE:
      return decoded_size != desired_size;
    case ImageLoadingTracker::ImageRepresentation::RESIZE_WHEN_LARGER:
      return decoded_size.width() > desired_size.width() ||
             decoded_size.height() > desired_size.height();
    default:
      NOTREACHED();
      return false;
  }
}

}  // namespace

////////////////////////////////////////////////////////////////////////////////
// ImageLoadingTracker::Observer

ImageLoadingTracker::Observer::~Observer() {}

////////////////////////////////////////////////////////////////////////////////
// ImageLoadingTracker::ImageRepresentation

ImageLoadingTracker::ImageRepresentation::ImageRepresentation(
    const ExtensionResource& resource,
    ResizeCondition resize_method,
    const gfx::Size& desired_size,
    ui::ScaleFactor scale_factor)
    : resource(resource),
      resize_method(resize_method),
      desired_size(desired_size),
      scale_factor(scale_factor) {
}

ImageLoadingTracker::ImageRepresentation::~ImageRepresentation() {
}

////////////////////////////////////////////////////////////////////////////////
// ImageLoadingTracker::PendingLoadInfo

ImageLoadingTracker::PendingLoadInfo::PendingLoadInfo()
  : extension(NULL),
    cache(CACHE),
    pending_count(0) {
}

ImageLoadingTracker::PendingLoadInfo::~PendingLoadInfo() {}

////////////////////////////////////////////////////////////////////////////////
// ImageLoadingTracker::ImageLoader

// A RefCounted class for loading bitmaps/image reps on the File thread and
// reporting back on the UI thread.
class ImageLoadingTracker::ImageLoader
    : public base::RefCountedThreadSafe<ImageLoader> {
 public:
  explicit ImageLoader(ImageLoadingTracker* tracker)
      : tracker_(tracker) {
    CHECK(BrowserThread::GetCurrentThreadIdentifier(&callback_thread_id_));
    DCHECK(!BrowserThread::GetBlockingPool()->RunsTasksOnCurrentThread());
  }

  // Lets this class know that the tracker is no longer interested in the
  // results.
  void StopTracking() {
    tracker_ = NULL;
  }

  // Instructs the loader to load a task on the File thread.
  void LoadImage(const ImageRepresentation& image_info, int id) {
    DCHECK(BrowserThread::CurrentlyOn(callback_thread_id_));
    BrowserThread::PostBlockingPoolTask(
        FROM_HERE,
        base::Bind(&ImageLoader::LoadOnBlockingPool, this, image_info, id));
  }

  void LoadOnBlockingPool(const ImageRepresentation& image_info, int id) {
    DCHECK(BrowserThread::GetBlockingPool()->RunsTasksOnCurrentThread());

    // Read the file from disk.
    std::string file_contents;
    FilePath path = image_info.resource.GetFilePath();
    if (path.empty() || !file_util::ReadFileToString(path, &file_contents)) {
      ReportBack(NULL, image_info, gfx::Size(), id);
      return;
    }

    // Decode the bitmap using WebKit's image decoder.
    const unsigned char* data =
        reinterpret_cast<const unsigned char*>(file_contents.data());
    webkit_glue::ImageDecoder decoder;
    scoped_ptr<SkBitmap> decoded(new SkBitmap());
    // Note: This class only decodes bitmaps from extension resources. Chrome
    // doesn't (for security reasons) directly load extension resources provided
    // by the extension author, but instead decodes them in a separate
    // locked-down utility process. Only if the decoding succeeds is the image
    // saved from memory to disk and subsequently used in the Chrome UI.
    // Chrome is therefore decoding bitmaps here that were generated by Chrome.
    *decoded = decoder.Decode(data, file_contents.length());
    if (decoded->empty()) {
      ReportBack(NULL, image_info, gfx::Size(), id);
      return;  // Unable to decode.
    }

    gfx::Size original_size(decoded->width(), decoded->height());
    *decoded = ResizeIfNeeded(*decoded, image_info);

    ReportBack(decoded.release(), image_info, original_size, id);
  }

  // Instructs the loader to load a resource on the File thread.
  void LoadResource(const ImageRepresentation& image_info,
                    int id,
                    int resource_id) {
    DCHECK(BrowserThread::CurrentlyOn(callback_thread_id_));
    BrowserThread::PostBlockingPoolTask(
        FROM_HERE,
        base::Bind(&ImageLoader::LoadResourceOnBlockingPool, this, image_info,
                   id, resource_id));
  }

  void LoadResourceOnBlockingPool(const ImageRepresentation& image_info,
                                  int id,
                                  int resource_id) {
    DCHECK(BrowserThread::GetBlockingPool()->RunsTasksOnCurrentThread());
    // TODO(xiyuan): Clean up to use SkBitmap here and in LoadOnBlockingPool.
    scoped_ptr<SkBitmap> bitmap(new SkBitmap);
    *bitmap = ResourceBundle::GetSharedInstance().GetImageNamed(
        resource_id).AsBitmap();

    *bitmap = ResizeIfNeeded(*bitmap, image_info);
    ReportBack(bitmap.release(), image_info, image_info.desired_size, id);
  }

  void ReportBack(const SkBitmap* bitmap, const ImageRepresentation& image_info,
                  const gfx::Size& original_size, int id) {
    DCHECK(BrowserThread::GetBlockingPool()->RunsTasksOnCurrentThread());

    BrowserThread::PostTask(
        callback_thread_id_, FROM_HERE,
        base::Bind(&ImageLoader::ReportOnCallingThread, this,
                   bitmap, image_info, original_size, id));
  }

  void ReportOnCallingThread(const SkBitmap* bitmap,
                             const ImageRepresentation& image_info,
                             const gfx::Size& original_size,
                             int id) {
    DCHECK(BrowserThread::CurrentlyOn(callback_thread_id_));

    if (tracker_)
      tracker_->OnBitmapLoaded(bitmap, image_info, original_size, id, true);

    if (bitmap)
      delete bitmap;
  }

 private:
  friend class base::RefCountedThreadSafe<ImageLoader>;
  ~ImageLoader() {}

  SkBitmap ResizeIfNeeded(const SkBitmap& bitmap,
                          const ImageRepresentation& image_info) {
    gfx::Size original_size(bitmap.width(), bitmap.height());
    if (ShouldResizeImageRepresentation(image_info.resize_method,
                                        original_size,
                                        image_info.desired_size)) {
      return skia::ImageOperations::Resize(
          bitmap, skia::ImageOperations::RESIZE_LANCZOS3,
          image_info.desired_size.width(), image_info.desired_size.height());
    }

    return bitmap;
  }

  // The tracker we are loading the bitmap for. If NULL, it means the tracker is
  // no longer interested in the reply.
  ImageLoadingTracker* tracker_;

  // The thread that we need to call back on to report that we are done.
  BrowserThread::ID callback_thread_id_;

  DISALLOW_COPY_AND_ASSIGN(ImageLoader);
};

////////////////////////////////////////////////////////////////////////////////
// ImageLoadingTracker

// static
bool ImageLoadingTracker::IsSpecialBundledExtensionId(
    const std::string& extension_id) {
  int resource_id = -1;
  return FindSpecialExtensionResourceId(extension_id, &resource_id);
}

ImageLoadingTracker::ImageLoadingTracker(Observer* observer)
    : observer_(observer),
      next_id_(0) {
  registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_UNLOADED,
                 content::NotificationService::AllSources());
}

ImageLoadingTracker::~ImageLoadingTracker() {
  // The loader is created lazily and is NULL if the tracker is destroyed before
  // any valid image load tasks have been posted.
  if (loader_)
    loader_->StopTracking();
}

void ImageLoadingTracker::LoadImage(const Extension* extension,
                                    const ExtensionResource& resource,
                                    const gfx::Size& max_size,
                                    CacheParam cache) {
  std::vector<ImageRepresentation> info_list;
  info_list.push_back(ImageRepresentation(
      resource,
      ImageRepresentation::RESIZE_WHEN_LARGER,
      max_size,
      ui::SCALE_FACTOR_100P));
  LoadImages(extension, info_list, cache);
}

void ImageLoadingTracker::LoadImages(
    const Extension* extension,
    const std::vector<ImageRepresentation>& info_list,
    CacheParam cache) {
  PendingLoadInfo load_info;
  load_info.extension = extension;
  load_info.cache = cache;
  load_info.extension_id = extension->id();
  load_info.pending_count = info_list.size();
  int id = next_id_++;
  load_map_[id] = load_info;

  for (std::vector<ImageRepresentation>::const_iterator it = info_list.begin();
       it != info_list.end(); ++it) {
    int resource_id = -1;

    // Load resources for special component extensions.
    if (FindSpecialExtensionResourceId(load_info.extension_id, &resource_id)) {
      if (!loader_)
        loader_ = new ImageLoader(this);
      loader_->LoadResource(*it, id, resource_id);
      continue;
    }

    // If we don't have a path we don't need to do any further work, just
    // respond back.
    if (it->resource.relative_path().empty()) {
      OnBitmapLoaded(NULL, *it, it->desired_size, id, false);
      continue;
    }

    DCHECK(extension->path() == it->resource.extension_root());

    // See if the extension has the bitmap already.
    if (extension->HasCachedImage(it->resource, it->desired_size)) {
      SkBitmap bitmap = extension->GetCachedImage(it->resource,
                                                  it->desired_size);
      OnBitmapLoaded(&bitmap, *it, it->desired_size, id, false);
      continue;
    }

    // Instruct the ImageLoader to load this on the File thread. LoadImage and
    // LoadResource do not block.
    if (!loader_)
      loader_ = new ImageLoader(this);

    if (IsComponentExtensionResource(extension, it->resource, resource_id))
      loader_->LoadResource(*it, id, resource_id);
    else
      loader_->LoadImage(*it, id);
  }
}

bool ImageLoadingTracker::IsComponentExtensionResource(
    const Extension* extension,
    const ExtensionResource& resource,
    int& resource_id) const {
  if (extension->location() != Extension::COMPONENT)
    return false;

  FilePath directory_path = extension->path();
  FilePath relative_path = directory_path.BaseName().Append(
      resource.relative_path());

  for (size_t i = 0; i < kComponentExtensionResourcesSize; ++i) {
    FilePath resource_path =
        FilePath().AppendASCII(kComponentExtensionResources[i].name);
    resource_path = resource_path.NormalizePathSeparators();

    if (relative_path == resource_path) {
      resource_id = kComponentExtensionResources[i].value;
      return true;
    }
  }
  return false;
}

void ImageLoadingTracker::OnBitmapLoaded(
    const SkBitmap* bitmap,
    const ImageRepresentation& image_info,
    const gfx::Size& original_size,
    int id,
    bool should_cache) {
  LoadMap::iterator load_map_it = load_map_.find(id);
  DCHECK(load_map_it != load_map_.end());
  PendingLoadInfo* info = &load_map_it->second;

  // Save the pending results.
  DCHECK_GT(info->pending_count, 0u);
  info->pending_count--;
  if (bitmap) {
    info->image_skia.AddRepresentation(gfx::ImageSkiaRep(*bitmap,
                                       image_info.scale_factor));
  }

  // Add to the extension's bitmap cache if requested.
  DCHECK(info->cache != CACHE || info->extension);
  if (should_cache && info->cache == CACHE  && !image_info.resource.empty() &&
      !info->extension->HasCachedImage(image_info.resource, original_size)) {
    info->extension->SetCachedImage(image_info.resource,
                                    bitmap ? *bitmap : SkBitmap(),
                                    original_size);
  }

  // If all pending bitmaps are done then report back.
  if (info->pending_count == 0) {
    gfx::Image image;
    std::string extension_id = info->extension_id;

    if (!info->image_skia.isNull()) {
      info->image_skia.MakeThreadSafe();
      image = gfx::Image(info->image_skia);
    }

    load_map_.erase(load_map_it);

    // ImageLoadingTracker might be deleted after the callback so don't do
    // anything after this statement.
    observer_->OnImageLoaded(image, extension_id, id);
  }
}

void ImageLoadingTracker::Observe(int type,
                                  const content::NotificationSource& source,
                                  const content::NotificationDetails& details) {
  DCHECK(type == chrome::NOTIFICATION_EXTENSION_UNLOADED);

  const Extension* extension =
      content::Details<extensions::UnloadedExtensionInfo>(details)->extension;

  // Remove reference to this extension from all pending load entries. This
  // ensures we don't attempt to cache the bitmap when the load completes.
  for (LoadMap::iterator i = load_map_.begin(); i != load_map_.end(); ++i) {
    PendingLoadInfo* info = &i->second;
    if (info->extension == extension) {
      info->extension = NULL;
      info->cache = DONT_CACHE;
    }
  }
}
<|endoftext|>


<commit_before>// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include <gtk/gtk.h>

#include "base/string_util.h"
#include "chrome/browser/bookmarks/bookmark_model.h"
#include "chrome/browser/profile.h"
#include "chrome/browser/gtk/bookmark_editor_gtk.h"
#include "chrome/browser/gtk/bookmark_tree_model.h"
#include "chrome/common/pref_names.h"
#include "chrome/common/pref_service.h"
#include "chrome/test/testing_profile.h"
#include "testing/gtest/include/gtest/gtest.h"

using base::Time;
using base::TimeDelta;
using bookmark_utils::GetTitleFromTreeIter;

// Base class for bookmark editor tests. This class is a copy from
// bookmark_editor_view_unittest.cc, and all the tests in this file are
// GTK-ifications of the corresponding views tests. Testing here is really
// important because on Linux, we make round trip copies from chrome's
// BookmarkModel class to GTK's native GtkTreeStore.
class BookmarkEditorGtkTest : public testing::Test {
 public:
  BookmarkEditorGtkTest() : model_(NULL) {
  }

  virtual void SetUp() {
    profile_.reset(new TestingProfile());
    profile_->set_has_history_service(true);
    profile_->CreateBookmarkModel(true);

    model_ = profile_->GetBookmarkModel();

    AddTestData();
  }

  virtual void TearDown() {
  }

 protected:
  MessageLoopForUI message_loop_;
  BookmarkModel* model_;
  scoped_ptr<TestingProfile> profile_;

  std::string base_path() const { return "file:///c:/tmp/"; }

  BookmarkNode* GetNode(const std::string& name) {
    return model_->GetMostRecentlyAddedNodeForURL(GURL(base_path() + name));
  }

 private:
  // Creates the following structure:
  // bookmark bar node
  //   a
  //   F1
  //    f1a
  //    F11
  //     f11a
  //   F2
  // other node
  //   oa
  //   OF1
  //     of1a
  void AddTestData() {
    std::string test_base = base_path();

    model_->AddURL(model_->GetBookmarkBarNode(), 0, L"a",
                   GURL(test_base + "a"));
    BookmarkNode* f1 = model_->AddGroup(model_->GetBookmarkBarNode(), 1, L"F1");
    model_->AddURL(f1, 0, L"f1a", GURL(test_base + "f1a"));
    BookmarkNode* f11 = model_->AddGroup(f1, 1, L"F11");
    model_->AddURL(f11, 0, L"f11a", GURL(test_base + "f11a"));
    model_->AddGroup(model_->GetBookmarkBarNode(), 2, L"F2");

    // Children of the other node.
    model_->AddURL(model_->other_node(), 0, L"oa",
                   GURL(test_base + "oa"));
    BookmarkNode* of1 = model_->AddGroup(model_->other_node(), 1, L"OF1");
    model_->AddURL(of1, 0, L"of1a", GURL(test_base + "of1a"));
  }
};

// Makes sure the tree model matches that of the bookmark bar model.
TEST_F(BookmarkEditorGtkTest, ModelsMatch) {
  BookmarkEditorGtk editor(NULL, profile_.get(), NULL, NULL,
                           BookmarkEditor::SHOW_TREE, NULL);

  // The root should have two children, one for the bookmark bar node,
  // the other for the 'other bookmarks' folder.
  GtkTreeModel* store = GTK_TREE_MODEL(editor.tree_store_);
  GtkTreeIter toplevel;
  ASSERT_TRUE(gtk_tree_model_get_iter_first(store, &toplevel));
  GtkTreeIter bookmark_bar_node = toplevel;
  ASSERT_TRUE(gtk_tree_model_iter_next(store, &toplevel));
  GtkTreeIter other_node = toplevel;
  ASSERT_FALSE(gtk_tree_model_iter_next(store, &toplevel));

  // The bookmark bar should have 2 nodes: folder F1 and F2.
  GtkTreeIter f1_iter;
  GtkTreeIter child;
  ASSERT_EQ(2, gtk_tree_model_iter_n_children(store, &bookmark_bar_node));
  ASSERT_TRUE(gtk_tree_model_iter_children(store, &child, &bookmark_bar_node));
  f1_iter = child;
  ASSERT_EQ(L"F1", GetTitleFromTreeIter(store, &child));
  ASSERT_TRUE(gtk_tree_model_iter_next(store, &child));
  ASSERT_EQ(L"F2", GetTitleFromTreeIter(store, &child));
  ASSERT_FALSE(gtk_tree_model_iter_next(store, &child));

  // F1 should have one child, F11
  ASSERT_EQ(1, gtk_tree_model_iter_n_children(store, &f1_iter));
  ASSERT_TRUE(gtk_tree_model_iter_children(store, &child, &f1_iter));
  ASSERT_EQ(L"F11", GetTitleFromTreeIter(store, &child));
  ASSERT_FALSE(gtk_tree_model_iter_next(store, &child));

  // Other node should have one child (OF1).
  ASSERT_EQ(1, gtk_tree_model_iter_n_children(store, &other_node));
  ASSERT_TRUE(gtk_tree_model_iter_children(store, &child, &other_node));
  ASSERT_EQ(L"OF1", GetTitleFromTreeIter(store, &child));
  ASSERT_FALSE(gtk_tree_model_iter_next(store, &child));
}

// Changes the title and makes sure parent/visual order doesn't change.
TEST_F(BookmarkEditorGtkTest, EditTitleKeepsPosition) {
  BookmarkEditorGtk editor(NULL, profile_.get(), NULL, GetNode("a"),
                           BookmarkEditor::SHOW_TREE, NULL);
  gtk_entry_set_text(GTK_ENTRY(editor.name_entry_), "new_a");

  GtkTreeIter bookmark_bar_node;
  GtkTreeModel* store = GTK_TREE_MODEL(editor.tree_store_);
  ASSERT_TRUE(gtk_tree_model_get_iter_first(store, &bookmark_bar_node));
  editor.ApplyEdits(&bookmark_bar_node);

  BookmarkNode* bb_node = profile_->GetBookmarkModel()->GetBookmarkBarNode();
  ASSERT_EQ(L"new_a", bb_node->GetChild(0)->GetTitle());
  // The URL shouldn't have changed.
  ASSERT_TRUE(GURL(base_path() + "a") == bb_node->GetChild(0)->GetURL());
}

// Changes the url and makes sure parent/visual order doesn't change.
TEST_F(BookmarkEditorGtkTest, EditURLKeepsPosition) {
  Time node_time = Time::Now() + TimeDelta::FromDays(2);
  GetNode("a")->date_added_ = node_time;
  BookmarkEditorGtk editor(NULL, profile_.get(), NULL, GetNode("a"),
                           BookmarkEditor::SHOW_TREE, NULL);
  gtk_entry_set_text(GTK_ENTRY(editor.url_entry_),
                     GURL(base_path() + "new_a").spec().c_str());

  GtkTreeIter bookmark_bar_node;
  GtkTreeModel* store = GTK_TREE_MODEL(editor.tree_store_);
  ASSERT_TRUE(gtk_tree_model_get_iter_first(store, &bookmark_bar_node));
  editor.ApplyEdits(&bookmark_bar_node);

  BookmarkNode* bb_node = profile_->GetBookmarkModel()->GetBookmarkBarNode();
  ASSERT_EQ(L"a", bb_node->GetChild(0)->GetTitle());
  // The URL should have changed.
  ASSERT_TRUE(GURL(base_path() + "new_a") == bb_node->GetChild(0)->GetURL());
  ASSERT_TRUE(node_time == bb_node->GetChild(0)->date_added());
}

// Moves 'a' to be a child of the other node.
TEST_F(BookmarkEditorGtkTest, ChangeParent) {
  BookmarkEditorGtk editor(NULL, profile_.get(), NULL, GetNode("a"),
                           BookmarkEditor::SHOW_TREE, NULL);

  GtkTreeModel* store = GTK_TREE_MODEL(editor.tree_store_);
  GtkTreeIter gtk_other_node;
  ASSERT_TRUE(gtk_tree_model_get_iter_first(store, &gtk_other_node));
  ASSERT_TRUE(gtk_tree_model_iter_next(store, &gtk_other_node));
  editor.ApplyEdits(&gtk_other_node);

  BookmarkNode* other_node = profile_->GetBookmarkModel()->other_node();
  ASSERT_EQ(L"a", other_node->GetChild(2)->GetTitle());
  ASSERT_TRUE(GURL(base_path() + "a") == other_node->GetChild(2)->GetURL());
}

// Moves 'a' to be a child of the other node.
// Moves 'a' to be a child of the other node and changes its url to new_a.
TEST_F(BookmarkEditorGtkTest, ChangeParentAndURL) {
  Time node_time = Time::Now() + TimeDelta::FromDays(2);
  GetNode("a")->date_added_ = node_time;
  BookmarkEditorGtk editor(NULL, profile_.get(), NULL, GetNode("a"),
                           BookmarkEditor::SHOW_TREE, NULL);

  gtk_entry_set_text(GTK_ENTRY(editor.url_entry_),
                     GURL(base_path() + "new_a").spec().c_str());

  GtkTreeModel* store = GTK_TREE_MODEL(editor.tree_store_);
  GtkTreeIter gtk_other_node;
  ASSERT_TRUE(gtk_tree_model_get_iter_first(store, &gtk_other_node));
  ASSERT_TRUE(gtk_tree_model_iter_next(store, &gtk_other_node));
  editor.ApplyEdits(&gtk_other_node);

  BookmarkNode* other_node = profile_->GetBookmarkModel()->other_node();
  ASSERT_EQ(L"a", other_node->GetChild(2)->GetTitle());
  ASSERT_TRUE(GURL(base_path() + "new_a") == other_node->GetChild(2)->GetURL());
  ASSERT_TRUE(node_time == other_node->GetChild(2)->date_added());
}

// Creates a new folder and moves a node to it.
TEST_F(BookmarkEditorGtkTest, MoveToNewParent) {
  BookmarkEditorGtk editor(NULL, profile_.get(), NULL, GetNode("a"),
                           BookmarkEditor::SHOW_TREE, NULL);

  GtkTreeIter bookmark_bar_node;
  GtkTreeModel* store = GTK_TREE_MODEL(editor.tree_store_);
  ASSERT_TRUE(gtk_tree_model_get_iter_first(store, &bookmark_bar_node));

  // The bookmark bar should have 2 nodes: folder F1 and F2.
  GtkTreeIter f2_iter;
  ASSERT_EQ(2, gtk_tree_model_iter_n_children(store, &bookmark_bar_node));
  ASSERT_TRUE(gtk_tree_model_iter_children(store, &f2_iter,
                                           &bookmark_bar_node));
  ASSERT_TRUE(gtk_tree_model_iter_next(store, &f2_iter));

  // Create two nodes: "F21" as a child of "F2" and "F211" as a child of "F21".
  GtkTreeIter f21_iter;
  editor.AddNewGroup(&f2_iter, &f21_iter);
  gtk_tree_store_set(editor.tree_store_, &f21_iter,
                     bookmark_utils::FOLDER_NAME, "F21", -1);
  GtkTreeIter f211_iter;
  editor.AddNewGroup(&f21_iter, &f211_iter);
  gtk_tree_store_set(editor.tree_store_, &f211_iter,
                     bookmark_utils::FOLDER_NAME, "F211", -1);

  ASSERT_EQ(1, gtk_tree_model_iter_n_children(store, &f2_iter));

  editor.ApplyEdits(&f2_iter);

  BookmarkNode* bb_node = profile_->GetBookmarkModel()->GetBookmarkBarNode();
  BookmarkNode* mf2 = bb_node->GetChild(1);

  // F2 in the model should have two children now: F21 and the node edited.
  ASSERT_EQ(2, mf2->GetChildCount());
  // F21 should be first.
  ASSERT_EQ(L"F21", mf2->GetChild(0)->GetTitle());
  // Then a.
  ASSERT_EQ(L"a", mf2->GetChild(1)->GetTitle());

  // F21 should have one child, F211.
  BookmarkNode* mf21 = mf2->GetChild(0);
  ASSERT_EQ(1, mf21->GetChildCount());
  ASSERT_EQ(L"F211", mf21->GetChild(0)->GetTitle());
}

// Brings up the editor, creating a new URL on the bookmark bar.
TEST_F(BookmarkEditorGtkTest, NewURL) {
  BookmarkEditorGtk editor(NULL, profile_.get(), NULL, NULL,
                           BookmarkEditor::SHOW_TREE, NULL);

  gtk_entry_set_text(GTK_ENTRY(editor.url_entry_),
                     GURL(base_path() + "a").spec().c_str());
  gtk_entry_set_text(GTK_ENTRY(editor.name_entry_), "new_a");

  GtkTreeIter bookmark_bar_node;
  GtkTreeModel* store = GTK_TREE_MODEL(editor.tree_store_);
  ASSERT_TRUE(gtk_tree_model_get_iter_first(store, &bookmark_bar_node));
  editor.ApplyEdits(&bookmark_bar_node);

  BookmarkNode* bb_node = profile_->GetBookmarkModel()->GetBookmarkBarNode();
  ASSERT_EQ(4, bb_node->GetChildCount());

  BookmarkNode* new_node = bb_node->GetChild(3);
  EXPECT_EQ(L"new_a", new_node->GetTitle());
  EXPECT_TRUE(GURL(base_path() + "a") == new_node->GetURL());
}

// Brings up the editor with no tree and modifies the url.
TEST_F(BookmarkEditorGtkTest, ChangeURLNoTree) {
  BookmarkEditorGtk editor(NULL, profile_.get(), NULL,
                           model_->other_node()->GetChild(0),
                           BookmarkEditor::NO_TREE, NULL);

  gtk_entry_set_text(GTK_ENTRY(editor.url_entry_),
                     GURL(base_path() + "a").spec().c_str());
  gtk_entry_set_text(GTK_ENTRY(editor.name_entry_), "new_a");

  editor.ApplyEdits(NULL);

  BookmarkNode* other_node = profile_->GetBookmarkModel()->other_node();
  ASSERT_EQ(2, other_node->GetChildCount());

  BookmarkNode* new_node = other_node->GetChild(0);

  EXPECT_EQ(L"new_a", new_node->GetTitle());
  EXPECT_TRUE(GURL(base_path() + "a") == new_node->GetURL());
}

// Brings up the editor with no tree and modifies only the title.
TEST_F(BookmarkEditorGtkTest, ChangeTitleNoTree) {
  BookmarkEditorGtk editor(NULL, profile_.get(), NULL,
                           model_->other_node()->GetChild(0),
                           BookmarkEditor::NO_TREE, NULL);
  gtk_entry_set_text(GTK_ENTRY(editor.name_entry_), "new_a");

  editor.ApplyEdits();

  BookmarkNode* other_node = profile_->GetBookmarkModel()->other_node();
  ASSERT_EQ(2, other_node->GetChildCount());

  BookmarkNode* new_node = other_node->GetChild(0);
  EXPECT_EQ(L"new_a", new_node->GetTitle());
}
<commit_msg>Disable a failing test on linux.<commit_after>// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include <gtk/gtk.h>

#include "base/string_util.h"
#include "chrome/browser/bookmarks/bookmark_model.h"
#include "chrome/browser/profile.h"
#include "chrome/browser/gtk/bookmark_editor_gtk.h"
#include "chrome/browser/gtk/bookmark_tree_model.h"
#include "chrome/common/pref_names.h"
#include "chrome/common/pref_service.h"
#include "chrome/test/testing_profile.h"
#include "testing/gtest/include/gtest/gtest.h"

using base::Time;
using base::TimeDelta;
using bookmark_utils::GetTitleFromTreeIter;

// Base class for bookmark editor tests. This class is a copy from
// bookmark_editor_view_unittest.cc, and all the tests in this file are
// GTK-ifications of the corresponding views tests. Testing here is really
// important because on Linux, we make round trip copies from chrome's
// BookmarkModel class to GTK's native GtkTreeStore.
class BookmarkEditorGtkTest : public testing::Test {
 public:
  BookmarkEditorGtkTest() : model_(NULL) {
  }

  virtual void SetUp() {
    profile_.reset(new TestingProfile());
    profile_->set_has_history_service(true);
    profile_->CreateBookmarkModel(true);

    model_ = profile_->GetBookmarkModel();

    AddTestData();
  }

  virtual void TearDown() {
  }

 protected:
  MessageLoopForUI message_loop_;
  BookmarkModel* model_;
  scoped_ptr<TestingProfile> profile_;

  std::string base_path() const { return "file:///c:/tmp/"; }

  BookmarkNode* GetNode(const std::string& name) {
    return model_->GetMostRecentlyAddedNodeForURL(GURL(base_path() + name));
  }

 private:
  // Creates the following structure:
  // bookmark bar node
  //   a
  //   F1
  //    f1a
  //    F11
  //     f11a
  //   F2
  // other node
  //   oa
  //   OF1
  //     of1a
  void AddTestData() {
    std::string test_base = base_path();

    model_->AddURL(model_->GetBookmarkBarNode(), 0, L"a",
                   GURL(test_base + "a"));
    BookmarkNode* f1 = model_->AddGroup(model_->GetBookmarkBarNode(), 1, L"F1");
    model_->AddURL(f1, 0, L"f1a", GURL(test_base + "f1a"));
    BookmarkNode* f11 = model_->AddGroup(f1, 1, L"F11");
    model_->AddURL(f11, 0, L"f11a", GURL(test_base + "f11a"));
    model_->AddGroup(model_->GetBookmarkBarNode(), 2, L"F2");

    // Children of the other node.
    model_->AddURL(model_->other_node(), 0, L"oa",
                   GURL(test_base + "oa"));
    BookmarkNode* of1 = model_->AddGroup(model_->other_node(), 1, L"OF1");
    model_->AddURL(of1, 0, L"of1a", GURL(test_base + "of1a"));
  }
};

// Makes sure the tree model matches that of the bookmark bar model.
TEST_F(BookmarkEditorGtkTest, ModelsMatch) {
  BookmarkEditorGtk editor(NULL, profile_.get(), NULL, NULL,
                           BookmarkEditor::SHOW_TREE, NULL);

  // The root should have two children, one for the bookmark bar node,
  // the other for the 'other bookmarks' folder.
  GtkTreeModel* store = GTK_TREE_MODEL(editor.tree_store_);
  GtkTreeIter toplevel;
  ASSERT_TRUE(gtk_tree_model_get_iter_first(store, &toplevel));
  GtkTreeIter bookmark_bar_node = toplevel;
  ASSERT_TRUE(gtk_tree_model_iter_next(store, &toplevel));
  GtkTreeIter other_node = toplevel;
  ASSERT_FALSE(gtk_tree_model_iter_next(store, &toplevel));

  // The bookmark bar should have 2 nodes: folder F1 and F2.
  GtkTreeIter f1_iter;
  GtkTreeIter child;
  ASSERT_EQ(2, gtk_tree_model_iter_n_children(store, &bookmark_bar_node));
  ASSERT_TRUE(gtk_tree_model_iter_children(store, &child, &bookmark_bar_node));
  f1_iter = child;
  ASSERT_EQ(L"F1", GetTitleFromTreeIter(store, &child));
  ASSERT_TRUE(gtk_tree_model_iter_next(store, &child));
  ASSERT_EQ(L"F2", GetTitleFromTreeIter(store, &child));
  ASSERT_FALSE(gtk_tree_model_iter_next(store, &child));

  // F1 should have one child, F11
  ASSERT_EQ(1, gtk_tree_model_iter_n_children(store, &f1_iter));
  ASSERT_TRUE(gtk_tree_model_iter_children(store, &child, &f1_iter));
  ASSERT_EQ(L"F11", GetTitleFromTreeIter(store, &child));
  ASSERT_FALSE(gtk_tree_model_iter_next(store, &child));

  // Other node should have one child (OF1).
  ASSERT_EQ(1, gtk_tree_model_iter_n_children(store, &other_node));
  ASSERT_TRUE(gtk_tree_model_iter_children(store, &child, &other_node));
  ASSERT_EQ(L"OF1", GetTitleFromTreeIter(store, &child));
  ASSERT_FALSE(gtk_tree_model_iter_next(store, &child));
}

// Changes the title and makes sure parent/visual order doesn't change.
TEST_F(BookmarkEditorGtkTest, EditTitleKeepsPosition) {
  BookmarkEditorGtk editor(NULL, profile_.get(), NULL, GetNode("a"),
                           BookmarkEditor::SHOW_TREE, NULL);
  gtk_entry_set_text(GTK_ENTRY(editor.name_entry_), "new_a");

  GtkTreeIter bookmark_bar_node;
  GtkTreeModel* store = GTK_TREE_MODEL(editor.tree_store_);
  ASSERT_TRUE(gtk_tree_model_get_iter_first(store, &bookmark_bar_node));
  editor.ApplyEdits(&bookmark_bar_node);

  BookmarkNode* bb_node = profile_->GetBookmarkModel()->GetBookmarkBarNode();
  ASSERT_EQ(L"new_a", bb_node->GetChild(0)->GetTitle());
  // The URL shouldn't have changed.
  ASSERT_TRUE(GURL(base_path() + "a") == bb_node->GetChild(0)->GetURL());
}

// Changes the url and makes sure parent/visual order doesn't change.
TEST_F(BookmarkEditorGtkTest, EditURLKeepsPosition) {
  Time node_time = Time::Now() + TimeDelta::FromDays(2);
  GetNode("a")->date_added_ = node_time;
  BookmarkEditorGtk editor(NULL, profile_.get(), NULL, GetNode("a"),
                           BookmarkEditor::SHOW_TREE, NULL);
  gtk_entry_set_text(GTK_ENTRY(editor.url_entry_),
                     GURL(base_path() + "new_a").spec().c_str());

  GtkTreeIter bookmark_bar_node;
  GtkTreeModel* store = GTK_TREE_MODEL(editor.tree_store_);
  ASSERT_TRUE(gtk_tree_model_get_iter_first(store, &bookmark_bar_node));
  editor.ApplyEdits(&bookmark_bar_node);

  BookmarkNode* bb_node = profile_->GetBookmarkModel()->GetBookmarkBarNode();
  ASSERT_EQ(L"a", bb_node->GetChild(0)->GetTitle());
  // The URL should have changed.
  ASSERT_TRUE(GURL(base_path() + "new_a") == bb_node->GetChild(0)->GetURL());
  ASSERT_TRUE(node_time == bb_node->GetChild(0)->date_added());
}

// Moves 'a' to be a child of the other node.
TEST_F(BookmarkEditorGtkTest, ChangeParent) {
  BookmarkEditorGtk editor(NULL, profile_.get(), NULL, GetNode("a"),
                           BookmarkEditor::SHOW_TREE, NULL);

  GtkTreeModel* store = GTK_TREE_MODEL(editor.tree_store_);
  GtkTreeIter gtk_other_node;
  ASSERT_TRUE(gtk_tree_model_get_iter_first(store, &gtk_other_node));
  ASSERT_TRUE(gtk_tree_model_iter_next(store, &gtk_other_node));
  editor.ApplyEdits(&gtk_other_node);

  BookmarkNode* other_node = profile_->GetBookmarkModel()->other_node();
  ASSERT_EQ(L"a", other_node->GetChild(2)->GetTitle());
  ASSERT_TRUE(GURL(base_path() + "a") == other_node->GetChild(2)->GetURL());
}

// Moves 'a' to be a child of the other node.
// Moves 'a' to be a child of the other node and changes its url to new_a.
TEST_F(BookmarkEditorGtkTest, ChangeParentAndURL) {
  Time node_time = Time::Now() + TimeDelta::FromDays(2);
  GetNode("a")->date_added_ = node_time;
  BookmarkEditorGtk editor(NULL, profile_.get(), NULL, GetNode("a"),
                           BookmarkEditor::SHOW_TREE, NULL);

  gtk_entry_set_text(GTK_ENTRY(editor.url_entry_),
                     GURL(base_path() + "new_a").spec().c_str());

  GtkTreeModel* store = GTK_TREE_MODEL(editor.tree_store_);
  GtkTreeIter gtk_other_node;
  ASSERT_TRUE(gtk_tree_model_get_iter_first(store, &gtk_other_node));
  ASSERT_TRUE(gtk_tree_model_iter_next(store, &gtk_other_node));
  editor.ApplyEdits(&gtk_other_node);

  BookmarkNode* other_node = profile_->GetBookmarkModel()->other_node();
  ASSERT_EQ(L"a", other_node->GetChild(2)->GetTitle());
  ASSERT_TRUE(GURL(base_path() + "new_a") == other_node->GetChild(2)->GetURL());
  ASSERT_TRUE(node_time == other_node->GetChild(2)->date_added());
}

// TODO(tc): Test failing. Can't use DISABLED_ because of FRIEND_TEST.
#if 0
// Creates a new folder and moves a node to it.
TEST_F(BookmarkEditorGtkTest, MoveToNewParent) {
  BookmarkEditorGtk editor(NULL, profile_.get(), NULL, GetNode("a"),
                           BookmarkEditor::SHOW_TREE, NULL);

  GtkTreeIter bookmark_bar_node;
  GtkTreeModel* store = GTK_TREE_MODEL(editor.tree_store_);
  ASSERT_TRUE(gtk_tree_model_get_iter_first(store, &bookmark_bar_node));

  // The bookmark bar should have 2 nodes: folder F1 and F2.
  GtkTreeIter f2_iter;
  ASSERT_EQ(2, gtk_tree_model_iter_n_children(store, &bookmark_bar_node));
  ASSERT_TRUE(gtk_tree_model_iter_children(store, &f2_iter,
                                           &bookmark_bar_node));
  ASSERT_TRUE(gtk_tree_model_iter_next(store, &f2_iter));

  // Create two nodes: "F21" as a child of "F2" and "F211" as a child of "F21".
  GtkTreeIter f21_iter;
  editor.AddNewGroup(&f2_iter, &f21_iter);
  gtk_tree_store_set(editor.tree_store_, &f21_iter,
                     bookmark_utils::FOLDER_NAME, "F21", -1);
  GtkTreeIter f211_iter;
  editor.AddNewGroup(&f21_iter, &f211_iter);
  gtk_tree_store_set(editor.tree_store_, &f211_iter,
                     bookmark_utils::FOLDER_NAME, "F211", -1);

  ASSERT_EQ(1, gtk_tree_model_iter_n_children(store, &f2_iter));

  editor.ApplyEdits(&f2_iter);

  BookmarkNode* bb_node = profile_->GetBookmarkModel()->GetBookmarkBarNode();
  BookmarkNode* mf2 = bb_node->GetChild(1);

  // F2 in the model should have two children now: F21 and the node edited.
  ASSERT_EQ(2, mf2->GetChildCount());
  // F21 should be first.
  ASSERT_EQ(L"F21", mf2->GetChild(0)->GetTitle());
  // Then a.
  ASSERT_EQ(L"a", mf2->GetChild(1)->GetTitle());

  // F21 should have one child, F211.
  BookmarkNode* mf21 = mf2->GetChild(0);
  ASSERT_EQ(1, mf21->GetChildCount());
  ASSERT_EQ(L"F211", mf21->GetChild(0)->GetTitle());
}
#endif

// Brings up the editor, creating a new URL on the bookmark bar.
TEST_F(BookmarkEditorGtkTest, NewURL) {
  BookmarkEditorGtk editor(NULL, profile_.get(), NULL, NULL,
                           BookmarkEditor::SHOW_TREE, NULL);

  gtk_entry_set_text(GTK_ENTRY(editor.url_entry_),
                     GURL(base_path() + "a").spec().c_str());
  gtk_entry_set_text(GTK_ENTRY(editor.name_entry_), "new_a");

  GtkTreeIter bookmark_bar_node;
  GtkTreeModel* store = GTK_TREE_MODEL(editor.tree_store_);
  ASSERT_TRUE(gtk_tree_model_get_iter_first(store, &bookmark_bar_node));
  editor.ApplyEdits(&bookmark_bar_node);

  BookmarkNode* bb_node = profile_->GetBookmarkModel()->GetBookmarkBarNode();
  ASSERT_EQ(4, bb_node->GetChildCount());

  BookmarkNode* new_node = bb_node->GetChild(3);
  EXPECT_EQ(L"new_a", new_node->GetTitle());
  EXPECT_TRUE(GURL(base_path() + "a") == new_node->GetURL());
}

// Brings up the editor with no tree and modifies the url.
TEST_F(BookmarkEditorGtkTest, ChangeURLNoTree) {
  BookmarkEditorGtk editor(NULL, profile_.get(), NULL,
                           model_->other_node()->GetChild(0),
                           BookmarkEditor::NO_TREE, NULL);

  gtk_entry_set_text(GTK_ENTRY(editor.url_entry_),
                     GURL(base_path() + "a").spec().c_str());
  gtk_entry_set_text(GTK_ENTRY(editor.name_entry_), "new_a");

  editor.ApplyEdits(NULL);

  BookmarkNode* other_node = profile_->GetBookmarkModel()->other_node();
  ASSERT_EQ(2, other_node->GetChildCount());

  BookmarkNode* new_node = other_node->GetChild(0);

  EXPECT_EQ(L"new_a", new_node->GetTitle());
  EXPECT_TRUE(GURL(base_path() + "a") == new_node->GetURL());
}

// Brings up the editor with no tree and modifies only the title.
TEST_F(BookmarkEditorGtkTest, ChangeTitleNoTree) {
  BookmarkEditorGtk editor(NULL, profile_.get(), NULL,
                           model_->other_node()->GetChild(0),
                           BookmarkEditor::NO_TREE, NULL);
  gtk_entry_set_text(GTK_ENTRY(editor.name_entry_), "new_a");

  editor.ApplyEdits();

  BookmarkNode* other_node = profile_->GetBookmarkModel()->other_node();
  ASSERT_EQ(2, other_node->GetChildCount());

  BookmarkNode* new_node = other_node->GetChild(0);
  EXPECT_EQ(L"new_a", new_node->GetTitle());
}
<|endoftext|>


<commit_before>/*
Copyright (c) 2010, Dan Bethell, Johannes Saam.
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 RmanConnect 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.
 */

static const char* const CLASS = "RmanConnect";
static const char* const HELP =
        "Connects to renderman in a output through the corresponding display driver";

#include <time.h>
#include <stdio.h>
#include <vector>
#include <string>
#include <sstream>

#include "DDImage/Iop.h"
#include "DDImage/Row.h"
#include "DDImage/Thread.h"
#include "DDImage/Knobs.h"
#include "DDImage/DDMath.h"
using namespace DD::Image;

// TODO: format output automatically
// TODO: kill thread on exit nuke
// TODO: client port based on port parameter

#include "Data.h"
#include "Server.h"

// our default port
const int rmanconnect_default_port = 9201;

// our listener method
static void rmanConnectListen(unsigned index, unsigned nthreads, void* data);

/// @class Colour
/// @brief lightweight class for describing an individual pixel
class RmanColour
{
    public:
        RmanColour()
        {
            _val[0] = _val[1] = _val[2] = 0.f;
            _val[3] = 1.f;
        }

        float& operator[](int i){ return _val[i]; }
        const float& operator[](int i) const { return _val[i]; }

        // data
        float _val[4];
};

///=====
/// @class RmanBuffer
/// @brief describes an image buffer or pixels
class RmanBuffer
{
    public:
        RmanBuffer() :
            _width(0),
            _height(0)
        {
        }

        void init(const unsigned int width, const unsigned int height)
        {
            _width = width;
            _height = height;
            _data.resize(_width * _height);
        }

        RmanColour& get(unsigned int x, unsigned int y)
        {
            unsigned int index = (_width * y) + x;
            return _data[index];
        }

        const RmanColour& get(unsigned int x, unsigned int y) const
        {
            unsigned int index = (_width * y) + x;
            return _data[index];
        }

        const unsigned int size() const
        {
            return _data.size();
        }

        // data
        std::vector<RmanColour> _data;
        unsigned int _width;
        unsigned int _height;
};
///=====

///=====
/// @class RmanConnect
/// @brief our nuke node that receives data from an rman display driver
class RmanConnect: public Iop
{
    public:
        FormatPair m_fmt; // our buffer format (knob)
        int m_port; // the port we're listening on (knob)

        RmanBuffer m_buffer; // our pixel buffer
        Lock m_mutex; // mutex for locking the pixel buffer
        unsigned int hash_counter; // our refresh hash counter
        rmanconnect::Server m_server; // our rmanconnect::Server
        bool m_inError; // some error handling
        std::string m_connectionError;
        bool m_legit;

        RmanConnect(Node* node) :
            Iop(node),
            m_port(rmanconnect_default_port),
            m_inError(false),
            m_connectionError(""),
            m_legit(false)
        {
            inputs(0);
        }

        // Apparently nodes get copied/constructed upon asapUpdate() and this causes a few
        // problems - we don't want new sockets getting opened etc.
        // Fortunately attach() only gets called for nodes in the dag so we can
        // use this to mark the DAG node as 'legit' and open the port accordingly.
        void attach()
        {
            m_legit = true;
        }

        // we can use this to change our tcp port
        void changePort( int port )
        {
            m_inError = false;
            m_connectionError = "";

            if ( m_server.isConnected() )
            {
                m_server.quit();
                Thread::wait(this);
            }

            // try to reconnect
            try
            {
                m_server.connect( m_port );
            }
            catch ( ... )
            {
                std::stringstream ss;
                ss << "Could not connect to port: " << port;
                m_connectionError = ss.str();
                m_inError = true;
                return;
            }

            // success
            if ( m_server.isConnected() )
            {
                Thread::spawn(::rmanConnectListen, 1, this);
                std::cout << "Connected to port: " << m_server.getPort() << std::endl;
            }
        }

        // Destroying the Op should get rid of the parallel threads.
        // Unfortunatly currently Nuke does not destroy one of the Ops on a
        // deleted node, as it is saving it for Undo. This bug will be fixed
        // in an upcoming version, so you should implement this:
        ~RmanConnect()
        {
            if ( m_server.isConnected() )
            {
                m_server.quit();
                Thread::wait(this);
            }
        }

        // The hash value must change or Nuke will think the picture is the
        // same. If you can't determine some id for the picture, you should
        // use the current time or something.
        void append(Hash& hash)
        {
            hash.append(hash_counter++);
        }

        void _validate(bool for_real)
        {
            // do we need to open a port?
            if ( m_server.isConnected()==false && !m_inError && m_legit )
            {
                std::cerr << "Change Port: " << m_port << std::endl;
                changePort(m_port);
            }

            // handle any connection error
            if ( m_inError )
                error(m_connectionError.c_str());

            // setup format etc
            info_.format(*m_fmt.fullSizeFormat());
            info_.full_size_format(*m_fmt.format());
            info_.channels(Mask_RGBA);
            info_.set(info().format());
        }

        void engine(int y, int xx, int r, ChannelMask channels, Row& out)
        {
            float *rOut = out.writable(Chan_Red) + xx;
            float *gOut = out.writable(Chan_Green) + xx;
            float *bOut = out.writable(Chan_Blue) + xx;
            float *aOut = out.writable(Chan_Alpha) + xx;
            const float *END = rOut + (r - xx);
            unsigned int xxx = static_cast<unsigned int> (xx);
            unsigned int yyy = static_cast<unsigned int> (y);

            // don't have a buffer yet
            if ( m_buffer._width==0 && m_buffer._height==0 )
            {
                while (rOut < END)
                {
                    *rOut = *gOut = *bOut = *aOut = 0.f;
                    ++rOut;
                    ++gOut;
                    ++bOut;
                    ++aOut;
                    ++xxx;
                }
            }
            else
            {
                while (rOut < END)
                {
                    if ( xxx >= m_buffer._width || yyy >= m_buffer._height )
                    {
                        *rOut = *gOut = *bOut = *aOut = 0.f;
                    }
                    else
                    {
                        *rOut = m_buffer.get(xxx, yyy)[0];
                        *gOut = m_buffer.get(xxx, yyy)[1];
                        *bOut = m_buffer.get(xxx, yyy)[2];
                        *aOut = m_buffer.get(xxx, yyy)[3];
                    }
                    ++rOut;
                    ++gOut;
                    ++bOut;
                    ++aOut;
                    ++xxx;
                }
            }
        }

        void knobs(Knob_Callback f)
        {
            Format_knob(f, &m_fmt, "m_formats_knob", "format");
            Tooltip(f, "Output render format");
            Int_knob(f, &m_port, "port_number", "port");
        }

        int knob_changed(Knob* knob)
        {
            if (knob->name() && strcmp(knob->name(), "port_number") == 0)
            {
                changePort(m_port);
                return 1;
            }
            return 0;
        }

        const char* Class() const { return CLASS; }
        const char* displayName() const { return CLASS; }
        const char* node_help() const { return HELP; }
        static const Iop::Description desc;
};
//=====

//=====
// @brief our listening thread method
static void rmanConnectListen(unsigned index, unsigned nthreads, void* data)
{
    bool killThread = false;

    RmanConnect * node = reinterpret_cast<RmanConnect*> (data);
    if (!node->m_server.isConnected())
    {
        std::cerr << "Could not find opened RmanConnect port!" << std::endl;
        return;
    }

    while (!killThread)
    {
        // block here until we get some data
        rmanconnect::Data d = node->m_server.listen();

        // handle the data we received
        switch (d.type())
        {
            case 0: // open a new image
            {
                node->m_mutex.lock();
                node->m_buffer.init(d.width(), d.height());
                node->m_mutex.unlock();
                break;
            }
            case 1: // image data
            {
                // lock buffer
                node->m_mutex.lock();

                // copy data from d into node->m_buffer
                int _w = node->m_buffer._width;
                int _h = node->m_buffer._height;

                unsigned int _x, _x0, _y, _y0, _s, offset;
                _x = _x0 = _y = _y0 = _s = 0;

                int _xorigin = d.x();
                int _yorigin = d.y();
                int _width = d.width();
                int _height = d.height();
                int _spp = d.spp();

                const float* pixel_data = d.data();
                for (_x = 0; _x < _width; ++_x)
                    for (_y = 0; _y < _height; ++_y)
                    {
                        RmanColour &pix = node->m_buffer.get(_x
                                + _xorigin, _h - (_y + _yorigin + 1));
                        offset = (_width * _y * _spp) + (_x * _spp);
                        for (_s = 0; _s < _spp; ++_s)
                            pix[_s] = pixel_data[offset+_s];
                    }

                // TODO: ideally when 'd' goes out of scope this could be cleaned up for us
                delete[] pixel_data;

                // release buffer
                node->m_mutex.unlock();
                break;
            }
            case 2: // close image
            {
                // do nothing
                break;
            }
            case 9: // this is sent when the parent process want to kill
                    // the listening thread
            {
                killThread = true;
                break;
            }
        }

        // increment our hash_counter
        if ( node->hash_counter==UINT_MAX )
            node->hash_counter=0;
        else
            node->hash_counter++;
        node->asapUpdate();
    }
}

//=====
// nuke builder stuff
static Iop* constructor(Node* node){ return new RmanConnect(node); }
const Iop::Description RmanConnect::desc(CLASS, "Image/RmanConnect", constructor);
<commit_msg>Tidied up a little bit of the nuke code. Moved the hash update and asapUpdate() call into RmanConnect::flagForUpdate().<commit_after>/*
Copyright (c) 2010, Dan Bethell, Johannes Saam.
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 RmanConnect 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.
 */

static const char* const CLASS = "RmanConnect";
static const char* const HELP =
        "Connects to renderman in a output through the corresponding display driver";

#include <time.h>
#include <stdio.h>
#include <vector>
#include <string>
#include <sstream>

#include "DDImage/Iop.h"
#include "DDImage/Row.h"
#include "DDImage/Thread.h"
#include "DDImage/Knobs.h"
#include "DDImage/DDMath.h"
using namespace DD::Image;

// TODO: format output automatically
// TODO: kill thread on exit nuke
// TODO: client port based on port parameter

#include "Data.h"
#include "Server.h"

// our default port
const int rmanconnect_default_port = 9201;

// our listener method
static void rmanConnectListen(unsigned index, unsigned nthreads, void* data);

/// @class Colour
/// @brief lightweight class for describing an individual pixel
class RmanColour
{
    public:
        RmanColour()
        {
            _val[0] = _val[1] = _val[2] = 0.f;
            _val[3] = 1.f;
        }

        float& operator[](int i){ return _val[i]; }
        const float& operator[](int i) const { return _val[i]; }

        // data
        float _val[4];
};

///=====
/// @class RmanBuffer
/// @brief describes an image buffer or pixels
class RmanBuffer
{
    public:
        RmanBuffer() :
            _width(0),
            _height(0)
        {
        }

        void init(const unsigned int width, const unsigned int height)
        {
            _width = width;
            _height = height;
            _data.resize(_width * _height);
        }

        RmanColour& get(unsigned int x, unsigned int y)
        {
            unsigned int index = (_width * y) + x;
            return _data[index];
        }

        const RmanColour& get(unsigned int x, unsigned int y) const
        {
            unsigned int index = (_width * y) + x;
            return _data[index];
        }

        const unsigned int size() const
        {
            return _data.size();
        }

        // data
        std::vector<RmanColour> _data;
        unsigned int _width;
        unsigned int _height;
};
///=====

///=====
/// @class RmanConnect
/// @brief our nuke node that receives data from an rman display driver
class RmanConnect: public Iop
{
    public:
        FormatPair m_fmt; // our buffer format (knob)
        int m_port; // the port we're listening on (knob)

        RmanBuffer m_buffer; // our pixel buffer
        Lock m_mutex; // mutex for locking the pixel buffer
        unsigned int hash_counter; // our refresh hash counter
        rmanconnect::Server m_server; // our rmanconnect::Server
        bool m_inError; // some error handling
        std::string m_connectionError;
        bool m_legit;

        RmanConnect(Node* node) :
            Iop(node),
            m_port(rmanconnect_default_port),
            m_inError(false),
            m_connectionError(""),
            m_legit(false)
        {
            inputs(0);
        }

        // Destroying the Op should get rid of the parallel threads.
        // Unfortunatly currently Nuke does not destroy one of the Ops on a
        // deleted node, as it is saving it for Undo. This bug will be fixed
        // in an upcoming version, so you should implement this:
        ~RmanConnect()
        {
            if ( m_server.isConnected() )
            {
                m_server.quit();
                Thread::wait(this);
            }
        }

        // It seems additional instances of a node get copied/constructed upon
        // very frequent calls to asapUpdate() and this causes us a few
        // problems - we don't want new sockets getting opened etc.
        // Fortunately attach() only gets called for nodes in the dag so we can
        // use this to mark the DAG node as 'legit' and open the port accordingly.
        void attach()
        {
            m_legit = true;
        }

        void flagForUpdate()
        {
            if ( hash_counter==UINT_MAX )
                hash_counter=0;
            else
                hash_counter++;
            asapUpdate();
        }

        // we can use this to change our tcp port
        void changePort( int port )
        {
            m_inError = false;
            m_connectionError = "";

            if ( m_server.isConnected() )
            {
                m_server.quit();
                Thread::wait(this);
            }

            // try to reconnect
            try
            {
                m_server.connect( m_port );
            }
            catch ( ... )
            {
                std::stringstream ss;
                ss << "Could not connect to port: " << port;
                m_connectionError = ss.str();
                m_inError = true;
                return;
            }

            // success
            if ( m_server.isConnected() )
            {
                Thread::spawn(::rmanConnectListen, 1, this);
                std::cout << "Connected to port: " << m_server.getPort() << std::endl;
            }
        }

        // The hash value must change or Nuke will think the picture is the
        // same. If you can't determine some id for the picture, you should
        // use the current time or something.
        void append(Hash& hash)
        {
            hash.append(hash_counter);
        }

        void _validate(bool for_real)
        {
            // do we need to open a port?
            if ( m_server.isConnected()==false && !m_inError && m_legit )
                changePort(m_port);

            // handle any connection error
            if ( m_inError )
                error(m_connectionError.c_str());

            // setup format etc
            info_.format(*m_fmt.fullSizeFormat());
            info_.full_size_format(*m_fmt.format());
            info_.channels(Mask_RGBA);
            info_.set(info().format());
        }

        void engine(int y, int xx, int r, ChannelMask channels, Row& out)
        {
            float *rOut = out.writable(Chan_Red) + xx;
            float *gOut = out.writable(Chan_Green) + xx;
            float *bOut = out.writable(Chan_Blue) + xx;
            float *aOut = out.writable(Chan_Alpha) + xx;
            const float *END = rOut + (r - xx);
            unsigned int xxx = static_cast<unsigned int> (xx);
            unsigned int yyy = static_cast<unsigned int> (y);

            // don't have a buffer yet
            if ( m_buffer._width==0 && m_buffer._height==0 )
            {
                while (rOut < END)
                {
                    *rOut = *gOut = *bOut = *aOut = 0.f;
                    ++rOut;
                    ++gOut;
                    ++bOut;
                    ++aOut;
                    ++xxx;
                }
            }
            else
            {
                while (rOut < END)
                {
                    if ( xxx >= m_buffer._width || yyy >= m_buffer._height )
                    {
                        *rOut = *gOut = *bOut = *aOut = 0.f;
                    }
                    else
                    {
                        *rOut = m_buffer.get(xxx, yyy)[0];
                        *gOut = m_buffer.get(xxx, yyy)[1];
                        *bOut = m_buffer.get(xxx, yyy)[2];
                        *aOut = m_buffer.get(xxx, yyy)[3];
                    }
                    ++rOut;
                    ++gOut;
                    ++bOut;
                    ++aOut;
                    ++xxx;
                }
            }
        }

        void knobs(Knob_Callback f)
        {
            Format_knob(f, &m_fmt, "m_formats_knob", "format");
            Tooltip(f, "Output render format");
            Int_knob(f, &m_port, "port_number", "port");
        }

        int knob_changed(Knob* knob)
        {
            if (knob->name() && strcmp(knob->name(), "port_number") == 0)
            {
                changePort(m_port);
                return 1;
            }
            return 0;
        }

        const char* Class() const { return CLASS; }
        const char* displayName() const { return CLASS; }
        const char* node_help() const { return HELP; }
        static const Iop::Description desc;
};
//=====

//=====
// @brief our listening thread method
static void rmanConnectListen(unsigned index, unsigned nthreads, void* data)
{
    bool killThread = false;

    RmanConnect * node = reinterpret_cast<RmanConnect*> (data);
    while (!killThread)
    {
        // block here until we get some data
        rmanconnect::Data d = node->m_server.listen();

        // handle the data we received
        switch (d.type())
        {
            case 0: // open a new image
            {
                node->m_mutex.lock();
                node->m_buffer.init(d.width(), d.height());
                node->m_mutex.unlock();
                break;
            }
            case 1: // image data
            {
                // lock buffer
                node->m_mutex.lock();

                // copy data from d into node->m_buffer
                int _w = node->m_buffer._width;
                int _h = node->m_buffer._height;

                unsigned int _x, _x0, _y, _y0, _s, offset;
                _x = _x0 = _y = _y0 = _s = 0;

                int _xorigin = d.x();
                int _yorigin = d.y();
                int _width = d.width();
                int _height = d.height();
                int _spp = d.spp();

                const float* pixel_data = d.data();
                for (_x = 0; _x < _width; ++_x)
                    for (_y = 0; _y < _height; ++_y)
                    {
                        RmanColour &pix = node->m_buffer.get(_x
                                + _xorigin, _h - (_y + _yorigin + 1));
                        offset = (_width * _y * _spp) + (_x * _spp);
                        for (_s = 0; _s < _spp; ++_s)
                            pix[_s] = pixel_data[offset+_s];
                    }

                // TODO: ideally when 'd' goes out of scope this could be cleaned up for us
                delete[] pixel_data;

                // release buffer
                node->m_mutex.unlock();

                // update the image
                node->flagForUpdate();
                break;
            }
            case 2: // close image
            {
                // update the image
                node->flagForUpdate();
                break;
            }
            case 9: // this is sent when the parent process want to kill
                    // the listening thread
            {
                killThread = true;
                break;
            }
        }

    }
}

//=====
// nuke builder stuff
static Iop* constructor(Node* node){ return new RmanConnect(node); }
const Iop::Description RmanConnect::desc(CLASS, "Image/RmanConnect", constructor);
<|endoftext|>