<commit_before>#include "CNNBallDetector.h"
#include "Tools/CameraGeometry.h"

#include "Tools/PatchWork.h"
#include "Tools/BlackSpotExtractor.h"

#include "Classifier/DortmundCNN/CNN_dortmund.h"
#include "Classifier/DortmundCNN/CNN_dortmund2018.h"
#include "Classifier/DortmundCNN/CNN_dortmund2018_keras.h"

using namespace std;

CNNBallDetector::CNNBallDetector()
{
  DEBUG_REQUEST_REGISTER("Vision:CNNBallDetector:keyPoints", "draw key points extracted from integral image", false);
  DEBUG_REQUEST_REGISTER("Vision:CNNBallDetector:drawCandidates", "draw ball candidates", false);
  DEBUG_REQUEST_REGISTER("Vision:CNNBallDetector:drawCandidatesResizes", "draw ball candidates (resized)", false);
  DEBUG_REQUEST_REGISTER("Vision:CNNBallDetector:refinePatches", "draw refined ball key points", false);
  DEBUG_REQUEST_REGISTER("Vision:CNNBallDetector:drawPercepts", "draw ball percepts", false);
  DEBUG_REQUEST_REGISTER("Vision:CNNBallDetector:drawPatchContrast", "draw patch contrast (only when contrast-check is in use!", false);

  DEBUG_REQUEST_REGISTER("Vision:CNNBallDetector:extractPatches", "generate YUVC patches", false);

  DEBUG_REQUEST_REGISTER("Vision:CNNBallDetector:keyPointsBlack", "draw black key points extracted from integral image", false);

  DEBUG_REQUEST_REGISTER("Vision:CNNBallDetector:drawPatchInImage", "draw the gray-scale patch like it is passed to the CNN in the image", false);

  theBallKeyPointExtractor = registerModule<BallKeyPointExtractor>("BallKeyPointExtractor", true);
  getDebugParameterList().add(&params);

  setClassifier("bottom.json", "bottom.json");
}

CNNBallDetector::~CNNBallDetector()
{
  getDebugParameterList().remove(&params);
}

void CNNBallDetector::execute(CameraInfo::CameraID id)
{
  cameraID = id;
  getBallCandidates().reset();

  best.clear();
  // update parameter
  theBallKeyPointExtractor->getModuleT()->setParameter(params.keyDetector);
  theBallKeyPointExtractor->getModuleT()->setCameraId(cameraID);
  //theBallKeyPointExtractor->getModuleT()->calculateKeyPoints(best);
  theBallKeyPointExtractor->getModuleT()->calculateKeyPointsBetter(best);

  // update selected classifier from parameters
  // TODO: make it more efficient
  setClassifier(params.classifier, params.classifierClose);

  if(best.size() > 0) {
    calculateCandidates();
  }

  DEBUG_REQUEST("Vision:CNNBallDetector:refinePatches",
    for(BestPatchList::reverse_iterator i = best.rbegin(); i != best.rend(); ++i) {
      //BestPatchList::Patch p = theBallKeyPointExtractor->getModuleT()->refineKeyPoint(*i);
      RECT_PX(ColorClasses::red, (*i).min.x, (*i).min.y, (*i).max.x, (*i).max.y);
    }
  );

  DEBUG_REQUEST("Vision:CNNBallDetector:drawPercepts",
    for(MultiBallPercept::ConstABPIterator iter = getMultiBallPercept().begin(); iter != getMultiBallPercept().end(); iter++) {
      if((*iter).cameraId == cameraID) {
        CIRCLE_PX(ColorClasses::orange, (int)((*iter).centerInImage.x+0.5), (int)((*iter).centerInImage.y+0.5), (int)((*iter).radiusInImage+0.5));
      }
    }
  );

  DEBUG_REQUEST("Vision:CNNBallDetector:extractPatches",
    extractPatches();
  );

  if(params.numberOfExportBestPatches > 0) {
    extractPatches();
  }

  DEBUG_REQUEST("Vision:CNNBallDetector:keyPointsBlack",
    BestPatchList bbest;
    for(BestPatchList::reverse_iterator i = best.rbegin(); i != best.rend(); ++i) {
      bbest.clear();
      BlackSpotExtractor::calculateKeyPointsBlackBetter(getBallDetectorIntegralImage(), bbest, (*i).min.x, (*i).min.y, (*i).max.x, (*i).max.y);
      int idx = 0;
      for(BestPatchList::reverse_iterator j = bbest.rbegin(); j != bbest.rend(); ++j) {
        RECT_PX(ColorClasses::red, (*j).min.x, (*j).min.y, (*j).max.x, (*j).max.y);
        if(++idx > 3) {
          break;
        }
      }
    }
  );
}

void CNNBallDetector::setClassifier(const std::string& name, const std::string& nameClose)
{
  if(currentCNNName != name) {
    currentCNN = std::make_shared<fdeep::model>(fdeep::load_model("Config/" + name));
    currentCNNName = name;
  }
  if(currentCNNCloseName != nameClose) {
    currentCNNClose = std::make_shared<fdeep::model>(fdeep::load_model("Config/" + nameClose));
    currentCNNCloseName = nameClose;
  }
}

void CNNBallDetector::calculateCandidates()
{
  // the used patch size
  const int patch_size = 16;

  // NOTE: patches are sorted in the ascending order, so start from the end to get the best patches
  int index = 0;
  for(BestPatchList::reverse_iterator i = best.rbegin(); i != best.rend(); ++i)
  {
    if(getFieldPercept().getValidField().isInside((*i).min) && getFieldPercept().getValidField().isInside((*i).max))
    {
      // limit the max amount of evaluated keys
      if(index > params.maxNumberOfKeys) {
        break;
      }

      static BallCandidates::PatchYUVClassified patch((*i).min, (*i).max, patch_size);
      patch.min = (*i).min;
      patch.max = (*i).max;
      if(!getImage().isInside(patch.min) || !getImage().isInside(patch.max)) {
        continue;
      }

      //
      // add an additional border as post-processing
      int postBorder = (int)(patch.radius()*params.postBorderFactorFar);
      double selectedCNNThreshold = params.cnn.threshold;
      if(patch.width() >= params.postMaxCloseSize) // HACK: use patch size as estimate if close or far away
      {
        postBorder = (int)(patch.radius()*params.postBorderFactorClose);
        selectedCNNThreshold = params.cnn.thresholdClose;
      }

      // resize the patch if possible
      if(getImage().isInside(patch.min - postBorder) && getImage().isInside(patch.max + postBorder)) {
        patch.min -= postBorder;
        patch.max += postBorder;
      }

      // extract the pixels
      PatchWork::subsampling(getImage(), getFieldColorPercept(), patch);

      // (5) check contrast
      if(params.checkContrast)
      {
        //double stddev = PatchWork::calculateContrastIterative2nd(getImage(),getFieldColorPercept(),min.x,min.y,max.x,max.y,patch_size);
        double stddev = PatchWork::calculateContrastIterative2nd(patch);

        DEBUG_REQUEST("Vision:CNNBallDetector:drawPatchContrast",
          CANVAS(((cameraID == CameraInfo::Top)?"ImageTop":"ImageBottom"));
          PEN("FF0000", 1); // red
          Vector2i c = patch.center();
          CIRCLE( c.x, c.y, stddev / 5.0);
        );

        // skip this patch, if contrast doesn't fullfill minimum
        double selectedContrastMinimum = params.contrastMinimum;
        if(patch.width() >= params.postMaxCloseSize) {
          selectedContrastMinimum = params.contrastMinimumClose;
        }
        if(stddev <= selectedContrastMinimum) {
          continue;
        }
      }

      PatchWork::multiplyBrightness((cameraID == CameraInfo::Top) ?
            params.brightnessMultiplierTop : params.brightnessMultiplierBottom, patch);

      DEBUG_REQUEST("Vision:CNNBallDetector:drawPatchInImage",
        unsigned int offsetX = patch.min.x;
        unsigned int offsetY = patch.min.y;
        unsigned int pixelWidth = (unsigned int) ((double) (patch.max.x - patch.min.x) / (double) patch.size() + 0.5);

        for(unsigned int x = 0; x < patch.size(); x++) {
          for(unsigned int y = 0; y < patch.size(); y++)
          {
            unsigned char pixelY = patch.data[x*patch_size + y].pixel.y;

            // draw each image pixel this patch pixel occupies
            for(unsigned int px=0; px < pixelWidth; px++) {
              for(unsigned int py=0; py < pixelWidth; py++) {
                getDebugImageDrawings().drawPointToImage(pixelY, 128, 128,
                  static_cast<int>(offsetX + (x*pixelWidth) + px), offsetY + (y*pixelWidth) + py);
              }
            }
          }
        }
      );

      // run CNN
      stopwatch.start();

      std::shared_ptr<fdeep::model> cnn = currentCNN;
      if(patch.width() >= params.postMaxCloseSize) {
        cnn = currentCNNClose;
      }

      // create input data from patch (TODO: why not use a Y-patch directly and save the copy operation?)
      ASSERT(patch.size() == 16);
      fdeep::tensor5 inputTensor = fdeep::tensor5(fdeep::shape5(1,1, 16, 16, 1), 0);

      for(size_t x=0; x < patch.size(); x++) {
        for(size_t y=0; y < patch.size(); y++) {
          // TODO: check if x and y are correct
          // The average brightness should have value 0.0
          float value = (static_cast<float>((patch.data[patch.size() * x + y].pixel.y)) / 255.0f)
            - static_cast<float>(params.cnn.meanBrightness);
          inputTensor.set(0, 0, y, x, 0, value);
        }
      }

      std::vector<fdeep::tensor5> result = cnn->predict({inputTensor});

      bool found = false;
      double radius = 0.0;
      double x = 0.0;
      double y = 0.0;
      if(result.size() == 1) {
        radius = result[0].get(0,0,0,0,0);
        x = result[0].get(0,0,0,1,0);
        y = result[0].get(0,0,0,2,0);

        if(radius >= selectedCNNThreshold && x >= 0.0f && y >= 0.0f) {
          found = true;
        }
      }
      stopwatch.stop();
      stopwatch_values.push_back(static_cast<double>(stopwatch.lastValue) * 0.001);

      if (found /* && classifier->getBallConfidence() >= selectedCNNThreshold*/) {
        // adjust the center and radius of the patch
        Vector2i ballCenterInPatch(static_cast<int>(x * 16.0f), static_cast<int>(y*16.0f));
        addBallPercept(Vector2i(patch.center() + ballCenterInPatch), patch.radius() * (radius/0.5f));
      }

      DEBUG_REQUEST("Vision:CNNBallDetector:drawCandidates",
        // original patch
        RECT_PX(ColorClasses::skyblue, (*i).min.x, (*i).min.y, (*i).max.x, (*i).max.y);
        // possibly recised patch
        RECT_PX(ColorClasses::orange, patch.min.x, patch.min.y, patch.max.x, patch.max.y);
      );

      index++;
    } // end if in field
  } // end for

} // end calculateCandidates

void CNNBallDetector::extractPatches()
{
  int idx = 0;
  for(BestPatchList::reverse_iterator i = best.rbegin(); i != best.rend(); ++i)
  {
    if(idx >= params.numberOfExportBestPatches) {
      break;
    }
    int offset = ((*i).max.x - (*i).min.x)/4;
    Vector2i min = (*i).min - offset;
    Vector2i max = (*i).max + offset;

    if(getFieldPercept().getValidField().isInside(min) && getFieldPercept().getValidField().isInside(max))
    {
      BallCandidates::PatchYUVClassified& q = getBallCandidates().nextFreePatchYUVClassified();
      q.min = min;
      q.max = max;
      q.setSize(24);
      PatchWork::subsampling(getImage(), getFieldColorPercept(), q);

      idx++;
    }
  }
}

void CNNBallDetector::addBallPercept(const Vector2i& center, double radius)
{
  const double ballRadius = 50.0;
  MultiBallPercept::BallPercept ballPercept;

  if(CameraGeometry::imagePixelToFieldCoord(
		  getCameraMatrix(),
		  getImage().cameraInfo,
		  center.x,
		  center.y,
		  ballRadius,
		  ballPercept.positionOnField))
  {
    ballPercept.cameraId = cameraID;
    ballPercept.centerInImage = center;
    ballPercept.radiusInImage = radius;

    getMultiBallPercept().add(ballPercept);
    getMultiBallPercept().frameInfoWhenBallWasSeen = getFrameInfo();
  }
}

<commit_msg>Old values patch center values where always 0<commit_after>#include "CNNBallDetector.h"
#include "Tools/CameraGeometry.h"

#include "Tools/PatchWork.h"
#include "Tools/BlackSpotExtractor.h"

#include "Classifier/DortmundCNN/CNN_dortmund.h"
#include "Classifier/DortmundCNN/CNN_dortmund2018.h"
#include "Classifier/DortmundCNN/CNN_dortmund2018_keras.h"

using namespace std;

CNNBallDetector::CNNBallDetector()
{
  DEBUG_REQUEST_REGISTER("Vision:CNNBallDetector:keyPoints", "draw key points extracted from integral image", false);
  DEBUG_REQUEST_REGISTER("Vision:CNNBallDetector:drawCandidates", "draw ball candidates", false);
  DEBUG_REQUEST_REGISTER("Vision:CNNBallDetector:drawCandidatesResizes", "draw ball candidates (resized)", false);
  DEBUG_REQUEST_REGISTER("Vision:CNNBallDetector:refinePatches", "draw refined ball key points", false);
  DEBUG_REQUEST_REGISTER("Vision:CNNBallDetector:drawPercepts", "draw ball percepts", false);
  DEBUG_REQUEST_REGISTER("Vision:CNNBallDetector:drawPatchContrast", "draw patch contrast (only when contrast-check is in use!", false);

  DEBUG_REQUEST_REGISTER("Vision:CNNBallDetector:extractPatches", "generate YUVC patches", false);

  DEBUG_REQUEST_REGISTER("Vision:CNNBallDetector:keyPointsBlack", "draw black key points extracted from integral image", false);

  DEBUG_REQUEST_REGISTER("Vision:CNNBallDetector:drawPatchInImage", "draw the gray-scale patch like it is passed to the CNN in the image", false);

  theBallKeyPointExtractor = registerModule<BallKeyPointExtractor>("BallKeyPointExtractor", true);
  getDebugParameterList().add(&params);

  setClassifier("bottom.json", "bottom.json");
}

CNNBallDetector::~CNNBallDetector()
{
  getDebugParameterList().remove(&params);
}

void CNNBallDetector::execute(CameraInfo::CameraID id)
{
  cameraID = id;
  getBallCandidates().reset();

  best.clear();
  // update parameter
  theBallKeyPointExtractor->getModuleT()->setParameter(params.keyDetector);
  theBallKeyPointExtractor->getModuleT()->setCameraId(cameraID);
  //theBallKeyPointExtractor->getModuleT()->calculateKeyPoints(best);
  theBallKeyPointExtractor->getModuleT()->calculateKeyPointsBetter(best);

  // update selected classifier from parameters
  // TODO: make it more efficient
  setClassifier(params.classifier, params.classifierClose);

  if(best.size() > 0) {
    calculateCandidates();
  }

  DEBUG_REQUEST("Vision:CNNBallDetector:refinePatches",
    for(BestPatchList::reverse_iterator i = best.rbegin(); i != best.rend(); ++i) {
      //BestPatchList::Patch p = theBallKeyPointExtractor->getModuleT()->refineKeyPoint(*i);
      RECT_PX(ColorClasses::red, (*i).min.x, (*i).min.y, (*i).max.x, (*i).max.y);
    }
  );

  DEBUG_REQUEST("Vision:CNNBallDetector:drawPercepts",
    for(MultiBallPercept::ConstABPIterator iter = getMultiBallPercept().begin(); iter != getMultiBallPercept().end(); iter++) {
      if((*iter).cameraId == cameraID) {
        CIRCLE_PX(ColorClasses::orange, (int)((*iter).centerInImage.x+0.5), (int)((*iter).centerInImage.y+0.5), (int)((*iter).radiusInImage+0.5));
      }
    }
  );

  DEBUG_REQUEST("Vision:CNNBallDetector:extractPatches",
    extractPatches();
  );

  if(params.numberOfExportBestPatches > 0) {
    extractPatches();
  }

  DEBUG_REQUEST("Vision:CNNBallDetector:keyPointsBlack",
    BestPatchList bbest;
    for(BestPatchList::reverse_iterator i = best.rbegin(); i != best.rend(); ++i) {
      bbest.clear();
      BlackSpotExtractor::calculateKeyPointsBlackBetter(getBallDetectorIntegralImage(), bbest, (*i).min.x, (*i).min.y, (*i).max.x, (*i).max.y);
      int idx = 0;
      for(BestPatchList::reverse_iterator j = bbest.rbegin(); j != bbest.rend(); ++j) {
        RECT_PX(ColorClasses::red, (*j).min.x, (*j).min.y, (*j).max.x, (*j).max.y);
        if(++idx > 3) {
          break;
        }
      }
    }
  );
}

void CNNBallDetector::setClassifier(const std::string& name, const std::string& nameClose)
{
  if(currentCNNName != name) {
    currentCNN = std::make_shared<fdeep::model>(fdeep::load_model("Config/" + name));
    currentCNNName = name;
  }
  if(currentCNNCloseName != nameClose) {
    currentCNNClose = std::make_shared<fdeep::model>(fdeep::load_model("Config/" + nameClose));
    currentCNNCloseName = nameClose;
  }
}

void CNNBallDetector::calculateCandidates()
{
  // the used patch size
  const int patch_size = 16;

  // NOTE: patches are sorted in the ascending order, so start from the end to get the best patches
  int index = 0;
  for(BestPatchList::reverse_iterator i = best.rbegin(); i != best.rend(); ++i)
  {
    if(getFieldPercept().getValidField().isInside((*i).min) && getFieldPercept().getValidField().isInside((*i).max))
    {
      // limit the max amount of evaluated keys
      if(index > params.maxNumberOfKeys) {
        break;
      }

      static BallCandidates::PatchYUVClassified patch((*i).min, (*i).max, patch_size);
      patch.min = (*i).min;
      patch.max = (*i).max;
      if(!getImage().isInside(patch.min) || !getImage().isInside(patch.max)) {
        continue;
      }

      //
      // add an additional border as post-processing
      int postBorder = (int)(patch.radius()*params.postBorderFactorFar);
      double selectedCNNThreshold = params.cnn.threshold;
      if(patch.width() >= params.postMaxCloseSize) // HACK: use patch size as estimate if close or far away
      {
        postBorder = (int)(patch.radius()*params.postBorderFactorClose);
        selectedCNNThreshold = params.cnn.thresholdClose;
      }

      // resize the patch if possible
      if(getImage().isInside(patch.min - postBorder) && getImage().isInside(patch.max + postBorder)) {
        patch.min -= postBorder;
        patch.max += postBorder;
      }

      // extract the pixels
      PatchWork::subsampling(getImage(), getFieldColorPercept(), patch);

      // (5) check contrast
      if(params.checkContrast)
      {
        //double stddev = PatchWork::calculateContrastIterative2nd(getImage(),getFieldColorPercept(),min.x,min.y,max.x,max.y,patch_size);
        double stddev = PatchWork::calculateContrastIterative2nd(patch);

        DEBUG_REQUEST("Vision:CNNBallDetector:drawPatchContrast",
          CANVAS(((cameraID == CameraInfo::Top)?"ImageTop":"ImageBottom"));
          PEN("FF0000", 1); // red
          Vector2i c = patch.center();
          CIRCLE( c.x, c.y, stddev / 5.0);
        );

        // skip this patch, if contrast doesn't fullfill minimum
        double selectedContrastMinimum = params.contrastMinimum;
        if(patch.width() >= params.postMaxCloseSize) {
          selectedContrastMinimum = params.contrastMinimumClose;
        }
        if(stddev <= selectedContrastMinimum) {
          continue;
        }
      }

      PatchWork::multiplyBrightness((cameraID == CameraInfo::Top) ?
            params.brightnessMultiplierTop : params.brightnessMultiplierBottom, patch);

      DEBUG_REQUEST("Vision:CNNBallDetector:drawPatchInImage",
        unsigned int offsetX = patch.min.x;
        unsigned int offsetY = patch.min.y;
        unsigned int pixelWidth = (unsigned int) ((double) (patch.max.x - patch.min.x) / (double) patch.size() + 0.5);

        for(unsigned int x = 0; x < patch.size(); x++) {
          for(unsigned int y = 0; y < patch.size(); y++)
          {
            unsigned char pixelY = patch.data[x*patch_size + y].pixel.y;

            // draw each image pixel this patch pixel occupies
            for(unsigned int px=0; px < pixelWidth; px++) {
              for(unsigned int py=0; py < pixelWidth; py++) {
                getDebugImageDrawings().drawPointToImage(pixelY, 128, 128,
                  static_cast<int>(offsetX + (x*pixelWidth) + px), offsetY + (y*pixelWidth) + py);
              }
            }
          }
        }
      );

      // run CNN
      stopwatch.start();

      std::shared_ptr<fdeep::model> cnn = currentCNN;
      if(patch.width() >= params.postMaxCloseSize) {
        cnn = currentCNNClose;
      }

      // create input data from patch (TODO: why not use a Y-patch directly and save the copy operation?)
      ASSERT(patch.size() == 16);
      fdeep::tensor5 inputTensor = fdeep::tensor5(fdeep::shape5(1,1, 16, 16, 1), 0);

      for(size_t x=0; x < patch.size(); x++) {
        for(size_t y=0; y < patch.size(); y++) {
          // TODO: check if x and y are correct
          // The average brightness should have value 0.0
          float value = (static_cast<float>((patch.data[patch.size() * x + y].pixel.y)) / 255.0f)
            - static_cast<float>(params.cnn.meanBrightness);
          inputTensor.set(0, 0, y, x, 0, value);
        }
      }

      std::vector<fdeep::tensor5> result = cnn->predict({inputTensor});

      bool found = false;
      double radius = 0.0;
      double x = 0.0;
      double y = 0.0;
      if(result.size() == 1) {
        radius = result[0].get(0,0,0,0,0);
        x = result[0].get(0,0,0,0,1);
        y = result[0].get(0,0,0,0,2);

        if(radius >= selectedCNNThreshold && x >= 0.0f && y >= 0.0f) {
          found = true;

          //std::cout << fdeep::show_tensor5s(result) << " (radius=" << radius << " x=" << x << " y=" << y << ")" << std::endl;
        }
      }
      stopwatch.stop();
      stopwatch_values.push_back(static_cast<double>(stopwatch.lastValue) * 0.001);

      if (found /* && classifier->getBallConfidence() >= selectedCNNThreshold*/) {
        // adjust the center and radius of the patch
        Vector2i ballCenterInPatch(static_cast<int>(x * patch.width()), static_cast<int>(y*patch.width()));

        addBallPercept(patch.min + ballCenterInPatch, radius*patch.width());
      }

      DEBUG_REQUEST("Vision:CNNBallDetector:drawCandidates",
        // original patch
        RECT_PX(ColorClasses::skyblue, (*i).min.x, (*i).min.y, (*i).max.x, (*i).max.y);
        // possibly recised patch
        RECT_PX(ColorClasses::orange, patch.min.x, patch.min.y, patch.max.x, patch.max.y);
      );

      index++;
    } // end if in field
  } // end for

} // end calculateCandidates

void CNNBallDetector::extractPatches()
{
  int idx = 0;
  for(BestPatchList::reverse_iterator i = best.rbegin(); i != best.rend(); ++i)
  {
    if(idx >= params.numberOfExportBestPatches) {
      break;
    }
    int offset = ((*i).max.x - (*i).min.x)/4;
    Vector2i min = (*i).min - offset;
    Vector2i max = (*i).max + offset;

    if(getFieldPercept().getValidField().isInside(min) && getFieldPercept().getValidField().isInside(max))
    {
      BallCandidates::PatchYUVClassified& q = getBallCandidates().nextFreePatchYUVClassified();
      q.min = min;
      q.max = max;
      q.setSize(24);
      PatchWork::subsampling(getImage(), getFieldColorPercept(), q);

      idx++;
    }
  }
}

void CNNBallDetector::addBallPercept(const Vector2i& center, double radius)
{
  const double ballRadius = 50.0;
  MultiBallPercept::BallPercept ballPercept;

  if(CameraGeometry::imagePixelToFieldCoord(
		  getCameraMatrix(),
		  getImage().cameraInfo,
		  center.x,
		  center.y,
		  ballRadius,
		  ballPercept.positionOnField))
  {
    ballPercept.cameraId = cameraID;
    ballPercept.centerInImage = center;
    ballPercept.radiusInImage = radius;

    getMultiBallPercept().add(ballPercept);
    getMultiBallPercept().frameInfoWhenBallWasSeen = getFrameInfo();
  }
}

<|endoftext|>


<commit_before>#include <sstream>
#include <algorithm>
#include <set>
#include <memory>
#include <dlfcn.h>

#include "plugin-loader.hpp"
#include "output-layout.hpp"
#include "output.hpp"
#include "../core/wm.hpp"
#include "core.hpp"
#include "debug.hpp"

namespace
{
    template<class A, class B> B union_cast(A object)
    {
        union {
            A x;
            B y;
        } helper;
        helper.x = object;
        return helper.y;
    }
}

static const std::string default_plugins = "viewport_impl move resize animate \
                                            switcher vswitch cube expo command \
                                            grid";

plugin_manager::plugin_manager(wf::output_t *o, wayfire_config *config)
{
    this->config = config;
    this->output = o;

    auto section = config->get_section("core");
    plugins_opt = section->get_option("plugins", "none");

    reload_dynamic_plugins();
    load_static_plugins();

    list_updated = [=] ()
    {
        /* reload when config reload has finished */
        idle_reaload_plugins.run_once([&] () {reload_dynamic_plugins(); });
    };

    plugins_opt->updated.push_back(&list_updated);
}

void plugin_manager::deinit_plugins(bool unloadable)
{
    for (auto& p : loaded_plugins)
    {
        if (!p.second) // already destroyed on the previous iteration
            continue;

        if (p.second->is_unloadable() == unloadable)
            destroy_plugin(p.second);
    }
}

plugin_manager::~plugin_manager()
{
    /* First remove unloadable plugins, then others */
    deinit_plugins(true);
    deinit_plugins(false);

    loaded_plugins.clear();
    plugins_opt->updated.erase(
        std::remove(plugins_opt->updated.begin(), plugins_opt->updated.end(),
            &list_updated), plugins_opt->updated.end());
}

void plugin_manager::init_plugin(wayfire_plugin& p)
{
    p->grab_interface = std::make_unique<wf::plugin_grab_interface_t> (output);
    p->output = output;

    p->init(config);
}

void plugin_manager::destroy_plugin(wayfire_plugin& p)
{
    p->grab_interface->ungrab();
    output->deactivate_plugin(p->grab_interface);

    p->fini();

    /** dlopen()/dlclose() do reference counting */
    if (p->handle)
        dlclose(p->handle);

    p.reset();
}

wayfire_plugin plugin_manager::load_plugin_from_file(std::string path)
{
    // RTLD_GLOBAL is required for RTTI/dynamic_cast across plugins
    void *handle = dlopen(path.c_str(), RTLD_NOW | RTLD_GLOBAL);
    if(handle == NULL)
    {
        log_error("error loading plugin: %s", dlerror());
        return nullptr;
    }

    /* Check plugin version */
    auto version_func_ptr = dlsym(handle, "getWayfireVersion");
    if (version_func_ptr == NULL)
    {
        log_error("%s: missing getWayfireVersion()", path.c_str());
        dlclose(handle);
        return nullptr;
    }

    auto version_func =
        union_cast<void*, wayfire_plugin_version_func> (version_func_ptr);
    int32_t plugin_abi_version = version_func();

    if (version_func() != WAYFIRE_API_ABI_VERSION)
    {
        log_error("%s: API/ABI version mismatch: Wayfire is %d, plugin built "
            "with %d", path.c_str(), WAYFIRE_API_ABI_VERSION, plugin_abi_version);
        dlclose(handle);
        return nullptr;
    }

    auto new_instance_func_ptr = dlsym(handle, "newInstance");
    if(new_instance_func_ptr == NULL)
    {
        log_error("%s: missing newInstance(). %s", path.c_str(), dlerror());
        return nullptr;
    }

    log_debug("loading plugin %s", path.c_str());
    auto new_instance_func =
        union_cast<void*, wayfire_plugin_load_func> (new_instance_func_ptr);

    auto ptr = wayfire_plugin(new_instance_func());
    ptr->handle = handle;
    return ptr;
}

void plugin_manager::reload_dynamic_plugins()
{
    auto plugin_list = plugins_opt->as_string();
    if (plugin_list == "none")
    {
        log_error("No plugins specified in the config file, or config file is "
            "missing. In this state the compositor is nearly unusable, please "
            "ensure your configuration file is set up properly.");
        plugin_list = default_plugins;
    }

    std::stringstream stream(plugin_list);
    std::vector<std::string> next_plugins;

    auto plugin_prefix = std::string(INSTALL_PREFIX "/lib/wayfire/");

    std::string plugin_name;
    while(stream >> plugin_name)
    {
        if (plugin_name.size())
        {
            if (plugin_name.at(0) == '/')
                next_plugins.push_back(plugin_name);
            else
                next_plugins.push_back(plugin_prefix + "lib" + plugin_name + ".so");
        }
    }

    /* erase plugins that have been removed from the config */
    auto it = loaded_plugins.begin();
    while(it != loaded_plugins.end())
    {
        /* skip built-in(static) plugins */
        if (it->first.size() && it->first[0] == '_')
        {
            ++it;
            continue;
        }

        if (std::find(next_plugins.begin(), next_plugins.end(), it->first) == next_plugins.end() &&
            it->second->is_unloadable())
        {
            log_debug("unload plugin %s", it->first.c_str());
            destroy_plugin(it->second);
            it = loaded_plugins.erase(it);
        }
        else
        {
            ++it;
        }
    }

    /* load new plugins */
    for (auto plugin : next_plugins)
    {
        if (loaded_plugins.count(plugin))
            continue;

        auto ptr = load_plugin_from_file(plugin);
        if (ptr)
        {
            init_plugin(ptr);
            loaded_plugins[plugin] = std::move(ptr);
        }
    }
}

template<class T> static wayfire_plugin create_plugin()
{
    return std::unique_ptr<wf::plugin_interface_t>(new T);
}

void plugin_manager::load_static_plugins()
{
    loaded_plugins["_exit"]         = create_plugin<wayfire_exit>();
    loaded_plugins["_focus"]        = create_plugin<wayfire_focus>();
    loaded_plugins["_close"]        = create_plugin<wayfire_close>();
    loaded_plugins["_focus_parent"] = create_plugin<wayfire_handle_focus_parent>();

    init_plugin(loaded_plugins["_exit"]);
    init_plugin(loaded_plugins["_focus"]);
    init_plugin(loaded_plugins["_close"]);
    init_plugin(loaded_plugins["_focus_parent"]);
}
<commit_msg>plugin-loader: dlclose() after destroying the plugin entirely<commit_after>#include <sstream>
#include <algorithm>
#include <set>
#include <memory>
#include <dlfcn.h>

#include "plugin-loader.hpp"
#include "output-layout.hpp"
#include "output.hpp"
#include "../core/wm.hpp"
#include "core.hpp"
#include "debug.hpp"

namespace
{
    template<class A, class B> B union_cast(A object)
    {
        union {
            A x;
            B y;
        } helper;
        helper.x = object;
        return helper.y;
    }
}

static const std::string default_plugins = "viewport_impl move resize animate \
                                            switcher vswitch cube expo command \
                                            grid";

plugin_manager::plugin_manager(wf::output_t *o, wayfire_config *config)
{
    this->config = config;
    this->output = o;

    auto section = config->get_section("core");
    plugins_opt = section->get_option("plugins", "none");

    reload_dynamic_plugins();
    load_static_plugins();

    list_updated = [=] ()
    {
        /* reload when config reload has finished */
        idle_reaload_plugins.run_once([&] () {reload_dynamic_plugins(); });
    };

    plugins_opt->updated.push_back(&list_updated);
}

void plugin_manager::deinit_plugins(bool unloadable)
{
    for (auto& p : loaded_plugins)
    {
        if (!p.second) // already destroyed on the previous iteration
            continue;

        if (p.second->is_unloadable() == unloadable)
            destroy_plugin(p.second);
    }
}

plugin_manager::~plugin_manager()
{
    /* First remove unloadable plugins, then others */
    deinit_plugins(true);
    deinit_plugins(false);

    loaded_plugins.clear();
    plugins_opt->updated.erase(
        std::remove(plugins_opt->updated.begin(), plugins_opt->updated.end(),
            &list_updated), plugins_opt->updated.end());
}

void plugin_manager::init_plugin(wayfire_plugin& p)
{
    p->grab_interface = std::make_unique<wf::plugin_grab_interface_t> (output);
    p->output = output;

    p->init(config);
}

void plugin_manager::destroy_plugin(wayfire_plugin& p)
{
    p->grab_interface->ungrab();
    output->deactivate_plugin(p->grab_interface);

    p->fini();

    auto handle = p->handle;
    p.reset();

    /* dlopen()/dlclose() do reference counting, so we should close the plugin
     * as many times as we opened it.
     *
     * We also need to close the handle after deallocating the plugin, otherwise
     * we unload its destructor before calling it. */
    if (handle)
        dlclose(handle);
}

wayfire_plugin plugin_manager::load_plugin_from_file(std::string path)
{
    // RTLD_GLOBAL is required for RTTI/dynamic_cast across plugins
    void *handle = dlopen(path.c_str(), RTLD_NOW | RTLD_GLOBAL);
    if(handle == NULL)
    {
        log_error("error loading plugin: %s", dlerror());
        return nullptr;
    }

    /* Check plugin version */
    auto version_func_ptr = dlsym(handle, "getWayfireVersion");
    if (version_func_ptr == NULL)
    {
        log_error("%s: missing getWayfireVersion()", path.c_str());
        dlclose(handle);
        return nullptr;
    }

    auto version_func =
        union_cast<void*, wayfire_plugin_version_func> (version_func_ptr);
    int32_t plugin_abi_version = version_func();

    if (version_func() != WAYFIRE_API_ABI_VERSION)
    {
        log_error("%s: API/ABI version mismatch: Wayfire is %d, plugin built "
            "with %d", path.c_str(), WAYFIRE_API_ABI_VERSION, plugin_abi_version);
        dlclose(handle);
        return nullptr;
    }

    auto new_instance_func_ptr = dlsym(handle, "newInstance");
    if(new_instance_func_ptr == NULL)
    {
        log_error("%s: missing newInstance(). %s", path.c_str(), dlerror());
        return nullptr;
    }

    log_debug("loading plugin %s", path.c_str());
    auto new_instance_func =
        union_cast<void*, wayfire_plugin_load_func> (new_instance_func_ptr);

    auto ptr = wayfire_plugin(new_instance_func());
    ptr->handle = handle;
    return ptr;
}

void plugin_manager::reload_dynamic_plugins()
{
    auto plugin_list = plugins_opt->as_string();
    if (plugin_list == "none")
    {
        log_error("No plugins specified in the config file, or config file is "
            "missing. In this state the compositor is nearly unusable, please "
            "ensure your configuration file is set up properly.");
        plugin_list = default_plugins;
    }

    std::stringstream stream(plugin_list);
    std::vector<std::string> next_plugins;

    auto plugin_prefix = std::string(INSTALL_PREFIX "/lib/wayfire/");

    std::string plugin_name;
    while(stream >> plugin_name)
    {
        if (plugin_name.size())
        {
            if (plugin_name.at(0) == '/')
                next_plugins.push_back(plugin_name);
            else
                next_plugins.push_back(plugin_prefix + "lib" + plugin_name + ".so");
        }
    }

    /* erase plugins that have been removed from the config */
    auto it = loaded_plugins.begin();
    while(it != loaded_plugins.end())
    {
        /* skip built-in(static) plugins */
        if (it->first.size() && it->first[0] == '_')
        {
            ++it;
            continue;
        }

        if (std::find(next_plugins.begin(), next_plugins.end(), it->first) == next_plugins.end() &&
            it->second->is_unloadable())
        {
            log_debug("unload plugin %s", it->first.c_str());
            destroy_plugin(it->second);
            it = loaded_plugins.erase(it);
        }
        else
        {
            ++it;
        }
    }

    /* load new plugins */
    for (auto plugin : next_plugins)
    {
        if (loaded_plugins.count(plugin))
            continue;

        auto ptr = load_plugin_from_file(plugin);
        if (ptr)
        {
            init_plugin(ptr);
            loaded_plugins[plugin] = std::move(ptr);
        }
    }
}

template<class T> static wayfire_plugin create_plugin()
{
    return std::unique_ptr<wf::plugin_interface_t>(new T);
}

void plugin_manager::load_static_plugins()
{
    loaded_plugins["_exit"]         = create_plugin<wayfire_exit>();
    loaded_plugins["_focus"]        = create_plugin<wayfire_focus>();
    loaded_plugins["_close"]        = create_plugin<wayfire_close>();
    loaded_plugins["_focus_parent"] = create_plugin<wayfire_handle_focus_parent>();

    init_plugin(loaded_plugins["_exit"]);
    init_plugin(loaded_plugins["_focus"]);
    init_plugin(loaded_plugins["_close"]);
    init_plugin(loaded_plugins["_focus_parent"]);
}
<|endoftext|>


<commit_before>//===----------------------------------------------------------------------===//
//
//                         PelotonDB
//
// ddl_table.cpp
//
// Identification: src/backend/bridge/ddl/ddl_table.cpp
//
// Copyright (c) 2015, Carnegie Mellon University Database Group
//
//===----------------------------------------------------------------------===//

#include <cassert>
#include <iostream>
#include <vector>

#include "backend/bridge/ddl/ddl.h"
#include "backend/bridge/ddl/ddl_table.h"
#include "backend/bridge/ddl/ddl_database.h"
#include "backend/bridge/ddl/ddl_utils.h"
#include "backend/common/logger.h"
#include "backend/storage/backend_vm.h"
#include "backend/storage/table_factory.h"
#include "backend/storage/database.h"

#include "commands/dbcommands.h"
#include "nodes/pg_list.h"
#include "parser/parse_utilcmd.h"

namespace peloton {
namespace bridge {

//===--------------------------------------------------------------------===//
// Table DDL
//===--------------------------------------------------------------------===//

/**
 * @brief Execute the create stmt.
 * @param the parse tree
 * @param query string
 * @return true if we handled it correctly, false otherwise
 */

bool DDLTable::ExecCreateStmt(Node *parsetree,
                              std::vector<Node *> &parsetree_stack,
                              Peloton_Status *status, TransactionId txn_id) {
  List *stmts = ((CreateStmt *)parsetree)->stmts;

  /* ... and do it */
  ListCell *l;
  foreach (l, stmts) {
    Node *stmt = (Node *)lfirst(l);
    if (IsA(stmt, CreateStmt)) {
      CreateStmt *Cstmt = (CreateStmt *)stmt;
      List *schema = (List *)(Cstmt->tableElts);

      // Relation name and oid
      char *relation_name = Cstmt->relation->relname;
      Oid relation_oid = ((CreateStmt *)parsetree)->relation_id;

      assert(relation_oid);

      std::vector<catalog::Column> column_infos;
      std::vector<catalog::ForeignKey> foreign_keys;

      bool status;

      //===--------------------------------------------------------------------===//
      // CreateStmt --> ColumnInfo --> CreateTable
      //===--------------------------------------------------------------------===//
      if (schema != NULL) {
        DDLUtils::ParsingCreateStmt(Cstmt, column_infos, foreign_keys);

        DDLTable::CreateTable(relation_oid, relation_name, column_infos);
      }

      //===--------------------------------------------------------------------===//
      // Set Reference Tables
      //===--------------------------------------------------------------------===//
      status = DDLTable::SetReferenceTables(foreign_keys, relation_oid);
      if (status == false) {
        LOG_WARN("Failed to set reference tables");
      }
    }
  }

  //===--------------------------------------------------------------------===//
  // Rerun query
  //===--------------------------------------------------------------------===//
  for (auto parsetree : parsetree_stack) {
    DDL::ProcessUtility(parsetree, "rerun", status, txn_id);
  }
  parsetree_stack.clear();

  return true;
}
/**
 * @brief Execute the alter stmt.
 * @param the parsetree
 * @param the parsetree_stack store parsetree if the table is not created yet
 * @return true if we handled it correctly, false otherwise
 */
bool DDLTable::ExecAlterTableStmt(Node *parsetree,
                                  std::vector<Node *> &parsetree_stack) {
  AlterTableStmt *atstmt = (AlterTableStmt *)parsetree;

  Oid relation_oid = atstmt->relation_id;
  List *stmts = atstmt->stmts;

  // If table has not been created yet, store it into the parsetree stack
  auto &manager = catalog::Manager::GetInstance();
  storage::Database *db =
      manager.GetDatabaseWithOid(Bridge::GetCurrentDatabaseOid());
  if (nullptr == db->GetTableWithOid(relation_oid)) {
    parsetree_stack.push_back(parsetree);
    return true;
  }

  ListCell *l;
  foreach (l, stmts) {
    Node *stmt = (Node *)lfirst(l);
    if (IsA(stmt, AlterTableStmt)) {
      DDLTable::AlterTable(relation_oid, (AlterTableStmt *)stmt);
    }
  }
  return true;
}

/**
 * @brief Execute the drop stmt.
 * @param the parse tree
 * @return true if we handled it correctly, false otherwise
 */
bool DDLTable::ExecDropStmt(Node *parsetree) {
  DropStmt *drop = (DropStmt *)parsetree;
  // TODO drop->behavior;   /* RESTRICT or CASCADE behavior */

  ListCell *cell;
  foreach (cell, drop->objects) {
    List *names = ((List *)lfirst(cell));

    switch (drop->removeType) {
      case OBJECT_TABLE: {
        char *table_name = strVal(linitial(names));

        auto &manager = catalog::Manager::GetInstance();
        storage::Database *db =
            manager.GetDatabaseWithOid(Bridge::GetCurrentDatabaseOid());
        auto table = db->GetTableWithName(table_name);

        // skip if no table
        if (table == nullptr) break;

        Oid table_oid = table->GetOid();
        DDLTable::DropTable(table_oid);
      } break;

      default: {
        LOG_WARN("Unsupported drop object %d ", drop->removeType);
      } break;
    }
  }
  return true;
}

/**
 * @brief Create table.
 * @param table_name Table name
 * @param column_infos Information about the columns
 * @param schema Schema for the table
 * @return true if we created a table, false otherwise
 */
bool DDLTable::CreateTable(Oid relation_oid, std::string table_name,
                           std::vector<catalog::Column> column_infos,
                           catalog::Schema *schema) {
  assert(!table_name.empty());

  Oid database_oid = Bridge::GetCurrentDatabaseOid();
  if (database_oid == INVALID_OID || relation_oid == INVALID_OID) return false;

  // Get db oid
  auto &manager = catalog::Manager::GetInstance();
  storage::Database *db = manager.GetDatabaseWithOid(database_oid);

  // Construct our schema from vector of ColumnInfo
  if (schema == NULL) schema = new catalog::Schema(column_infos);

  // Build a table from schema
  storage::DataTable *table = storage::TableFactory::GetDataTable(
      database_oid, relation_oid, schema, table_name);

  db->AddTable(table);

  if (table != nullptr) {
    LOG_INFO("Created table(%u)%s in database(%u) ", relation_oid,
             table_name.c_str(), database_oid);
    return true;
  }
  return false;
}

/**
 * @brief AlterTable with given AlterTableStmt
 * @param relation_oid relation oid
 * @param Astmt AlterTableStmt
 * @return true if we alter the table successfully, false otherwise
 */
bool DDLTable::AlterTable(Oid relation_oid, AlterTableStmt *Astmt) {
  ListCell *lcmd;
  foreach (lcmd, Astmt->cmds) {
    AlterTableCmd *cmd = (AlterTableCmd *)lfirst(lcmd);

    switch (cmd->subtype) {
      // case AT_AddColumn:  /* add column */
      // case AT_DropColumn:  /* drop column */

      case AT_AddConstraint: /* ADD CONSTRAINT */
      {
        bool status = AddConstraint(relation_oid, (Constraint *)cmd->def);

        if (status == false) {
          LOG_WARN("Failed to add constraint");
        }
        break;
      }
      default:
        break;
    }
  }

  LOG_INFO("Alter the table (%u)\n", relation_oid);
  return true;
}

/**
 * @brief Drop table.
 * @param table_oid Table id.
 * @return true if we dropped the table, false otherwise
 */
// FIXME :: Dependencies btw indexes and tables
bool DDLTable::DropTable(Oid table_oid) {
  oid_t database_oid = Bridge::GetCurrentDatabaseOid();

  if (database_oid == InvalidOid || table_oid == InvalidOid) {
    LOG_WARN("Could not drop table :: db oid : %u table oid : %u", database_oid,
             table_oid);
    return false;
  }

  // Get db with current database oid
  auto &manager = catalog::Manager::GetInstance();
  storage::Database *db = manager.GetDatabaseWithOid(database_oid);

  db->DropTableWithOid(table_oid);

  LOG_INFO("Dropped table with oid : %u\n", table_oid);

  return true;
}

/**
 * @brief Add new constraint to the table
 * @param relation_oid relation oid
 * @param constraint constraint
 * @return true if we add the constraint, false otherwise
 */
bool DDLTable::AddConstraint(Oid relation_oid, Constraint *constraint) {
  ConstraintType contype = PostgresConstraintTypeToPelotonConstraintType(
      (PostgresConstraintType)constraint->contype);
  std::vector<catalog::ForeignKey> foreign_keys;
  std::string conname;

  if (constraint->conname != NULL) {
    conname = constraint->conname;
  } else {
    conname = "";
  }

  switch (contype) {
    case CONSTRAINT_TYPE_FOREIGN: {
      oid_t database_oid = Bridge::GetCurrentDatabaseOid();
      assert(database_oid);

      auto &manager = catalog::Manager::GetInstance();
      storage::Database *db = manager.GetDatabaseWithOid(database_oid);

      // PrimaryKey Table
      oid_t PrimaryKeyTableId =
          db->GetTableWithName(constraint->pktable->relname)->GetOid();

      // Each table column names
      std::vector<std::string> pk_column_names;
      std::vector<std::string> fk_column_names;

      ListCell *column;
      if (constraint->pk_attrs != NULL && constraint->pk_attrs->length > 0) {
        foreach (column, constraint->pk_attrs) {
          char *attname = strVal(lfirst(column));
          pk_column_names.push_back(attname);
        }
      }
      if (constraint->fk_attrs != NULL && constraint->fk_attrs->length > 0) {
        foreach (column, constraint->fk_attrs) {
          char *attname = strVal(lfirst(column));
          fk_column_names.push_back(attname);
        }
      }

      catalog::ForeignKey *foreign_key = new catalog::ForeignKey(
          PrimaryKeyTableId, pk_column_names, fk_column_names,
          constraint->fk_upd_action, constraint->fk_del_action, conname);
      foreign_keys.push_back(*foreign_key);

    } break;
    default:
      LOG_WARN("Unrecognized constraint type %d\n", (int)contype);
      break;
  }

  // FIXME :
  bool status = SetReferenceTables(foreign_keys, relation_oid);
  if (status == false) {
    LOG_WARN("Failed to set reference tables");
  }

  return true;
}

/**
 * @brief Set Reference Tables
 * @param reference table namees  reference table names
 * @param relation_oid relation oid
 * @return true if we set the reference tables, false otherwise
 */
bool DDLTable::SetReferenceTables(
    std::vector<catalog::ForeignKey> &foreign_keys, oid_t relation_oid) {
  assert(relation_oid);
  oid_t database_oid = Bridge::GetCurrentDatabaseOid();
  assert(database_oid);

  storage::DataTable *current_table =
      (storage::DataTable *)catalog::Manager::GetInstance().GetTableWithOid(
          database_oid, relation_oid);

  for (auto foreign_key : foreign_keys) {
    current_table->AddForeignKey(&foreign_key);
  }

  return true;
}

}  // namespace bridge
}  // namespace peloton
<commit_msg>Don't put nullptr into the vector..<commit_after>//===----------------------------------------------------------------------===//
//
//                         PelotonDB
//
// ddl_table.cpp
//
// Identification: src/backend/bridge/ddl/ddl_table.cpp
//
// Copyright (c) 2015, Carnegie Mellon University Database Group
//
//===----------------------------------------------------------------------===//

#include <cassert>
#include <iostream>
#include <vector>

#include "backend/bridge/ddl/ddl.h"
#include "backend/bridge/ddl/ddl_table.h"
#include "backend/bridge/ddl/ddl_database.h"
#include "backend/bridge/ddl/ddl_utils.h"
#include "backend/common/logger.h"
#include "backend/storage/backend_vm.h"
#include "backend/storage/table_factory.h"
#include "backend/storage/database.h"

#include "commands/dbcommands.h"
#include "nodes/pg_list.h"
#include "parser/parse_utilcmd.h"

namespace peloton {
namespace bridge {

//===--------------------------------------------------------------------===//
// Table DDL
//===--------------------------------------------------------------------===//

/**
 * @brief Execute the create stmt.
 * @param the parse tree
 * @param query string
 * @return true if we handled it correctly, false otherwise
 */

bool DDLTable::ExecCreateStmt(Node *parsetree,
                              std::vector<Node *> &parsetree_stack,
                              Peloton_Status *status, TransactionId txn_id) {
  List *stmts = ((CreateStmt *)parsetree)->stmts;

  /* ... and do it */
  ListCell *l;
  foreach (l, stmts) {
    Node *stmt = (Node *)lfirst(l);
    if (IsA(stmt, CreateStmt)) {
      CreateStmt *Cstmt = (CreateStmt *)stmt;
      List *schema = (List *)(Cstmt->tableElts);

      // Relation name and oid
      char *relation_name = Cstmt->relation->relname;
      Oid relation_oid = ((CreateStmt *)parsetree)->relation_id;

      assert(relation_oid);

      std::vector<catalog::Column> column_infos;
      std::vector<catalog::ForeignKey> foreign_keys;

      bool status;

      //===--------------------------------------------------------------------===//
      // CreateStmt --> ColumnInfo --> CreateTable
      //===--------------------------------------------------------------------===//
      if (schema != NULL) {
        DDLUtils::ParsingCreateStmt(Cstmt, column_infos, foreign_keys);

        DDLTable::CreateTable(relation_oid, relation_name, column_infos);
      }

      //===--------------------------------------------------------------------===//
      // Set Reference Tables
      //===--------------------------------------------------------------------===//
      status = DDLTable::SetReferenceTables(foreign_keys, relation_oid);
      if (status == false) {
        LOG_WARN("Failed to set reference tables");
      }
    }
  }

  //===--------------------------------------------------------------------===//
  // Rerun query
  //===--------------------------------------------------------------------===//
  for (auto parsetree : parsetree_stack) {
    DDL::ProcessUtility(parsetree, "rerun", status, txn_id);
  }
  parsetree_stack.clear();

  return true;
}
/**
 * @brief Execute the alter stmt.
 * @param the parsetree
 * @param the parsetree_stack store parsetree if the table is not created yet
 * @return true if we handled it correctly, false otherwise
 */
bool DDLTable::ExecAlterTableStmt(Node *parsetree,
                                  std::vector<Node *> &parsetree_stack) {
  AlterTableStmt *atstmt = (AlterTableStmt *)parsetree;

  Oid relation_oid = atstmt->relation_id;
  List *stmts = atstmt->stmts;

  // If table has not been created yet, store it into the parsetree stack
  auto &manager = catalog::Manager::GetInstance();
  storage::Database *db =
      manager.GetDatabaseWithOid(Bridge::GetCurrentDatabaseOid());
  if (nullptr == db->GetTableWithOid(relation_oid)) {
    parsetree_stack.push_back(parsetree);
    return true;
  }

  ListCell *l;
  foreach (l, stmts) {
    Node *stmt = (Node *)lfirst(l);
    if (IsA(stmt, AlterTableStmt)) {
      DDLTable::AlterTable(relation_oid, (AlterTableStmt *)stmt);
    }
  }
  return true;
}

/**
 * @brief Execute the drop stmt.
 * @param the parse tree
 * @return true if we handled it correctly, false otherwise
 */
bool DDLTable::ExecDropStmt(Node *parsetree) {
  DropStmt *drop = (DropStmt *)parsetree;
  // TODO drop->behavior;   /* RESTRICT or CASCADE behavior */

  ListCell *cell;
  foreach (cell, drop->objects) {
    List *names = ((List *)lfirst(cell));

    switch (drop->removeType) {
      case OBJECT_TABLE: {
        char *table_name = strVal(linitial(names));

        auto &manager = catalog::Manager::GetInstance();
        storage::Database *db =
            manager.GetDatabaseWithOid(Bridge::GetCurrentDatabaseOid());
        auto table = db->GetTableWithName(table_name);

        // skip if no table
        if (table == nullptr) break;

        Oid table_oid = table->GetOid();
        DDLTable::DropTable(table_oid);
      } break;

      default: {
        LOG_WARN("Unsupported drop object %d ", drop->removeType);
      } break;
    }
  }
  return true;
}

/**
 * @brief Create table.
 * @param table_name Table name
 * @param column_infos Information about the columns
 * @param schema Schema for the table
 * @return true if we created a table, false otherwise
 */
bool DDLTable::CreateTable(Oid relation_oid, std::string table_name,
                           std::vector<catalog::Column> column_infos,
                           catalog::Schema *schema) {
  assert(!table_name.empty());

  Oid database_oid = Bridge::GetCurrentDatabaseOid();
  if (database_oid == INVALID_OID || relation_oid == INVALID_OID) return false;

  // Get db oid
  auto &manager = catalog::Manager::GetInstance();
  storage::Database *db = manager.GetDatabaseWithOid(database_oid);

  // Construct our schema from vector of ColumnInfo
  if (schema == NULL) schema = new catalog::Schema(column_infos);

  // Build a table from schema
  storage::DataTable *table = storage::TableFactory::GetDataTable(
      database_oid, relation_oid, schema, table_name);

  if (table != nullptr) {
    LOG_INFO("Created table(%u)%s in database(%u) ", relation_oid,
             table_name.c_str(), database_oid);

    db->AddTable(table);
    return true;
  }
  return false;
}

/**
 * @brief AlterTable with given AlterTableStmt
 * @param relation_oid relation oid
 * @param Astmt AlterTableStmt
 * @return true if we alter the table successfully, false otherwise
 */
bool DDLTable::AlterTable(Oid relation_oid, AlterTableStmt *Astmt) {
  ListCell *lcmd;
  foreach (lcmd, Astmt->cmds) {
    AlterTableCmd *cmd = (AlterTableCmd *)lfirst(lcmd);

    switch (cmd->subtype) {
      // case AT_AddColumn:  /* add column */
      // case AT_DropColumn:  /* drop column */

      case AT_AddConstraint: /* ADD CONSTRAINT */
      {
        bool status = AddConstraint(relation_oid, (Constraint *)cmd->def);

        if (status == false) {
          LOG_WARN("Failed to add constraint");
        }
        break;
      }
      default:
        break;
    }
  }

  LOG_INFO("Alter the table (%u)\n", relation_oid);
  return true;
}

/**
 * @brief Drop table.
 * @param table_oid Table id.
 * @return true if we dropped the table, false otherwise
 */
// FIXME :: Dependencies btw indexes and tables
bool DDLTable::DropTable(Oid table_oid) {
  oid_t database_oid = Bridge::GetCurrentDatabaseOid();

  if (database_oid == InvalidOid || table_oid == InvalidOid) {
    LOG_WARN("Could not drop table :: db oid : %u table oid : %u", database_oid,
             table_oid);
    return false;
  }

  // Get db with current database oid
  auto &manager = catalog::Manager::GetInstance();
  storage::Database *db = manager.GetDatabaseWithOid(database_oid);

  db->DropTableWithOid(table_oid);

  LOG_INFO("Dropped table with oid : %u\n", table_oid);

  return true;
}

/**
 * @brief Add new constraint to the table
 * @param relation_oid relation oid
 * @param constraint constraint
 * @return true if we add the constraint, false otherwise
 */
bool DDLTable::AddConstraint(Oid relation_oid, Constraint *constraint) {
  ConstraintType contype = PostgresConstraintTypeToPelotonConstraintType(
      (PostgresConstraintType)constraint->contype);
  std::vector<catalog::ForeignKey> foreign_keys;
  std::string conname;

  if (constraint->conname != NULL) {
    conname = constraint->conname;
  } else {
    conname = "";
  }

  switch (contype) {
    case CONSTRAINT_TYPE_FOREIGN: {
      oid_t database_oid = Bridge::GetCurrentDatabaseOid();
      assert(database_oid);

      auto &manager = catalog::Manager::GetInstance();
      storage::Database *db = manager.GetDatabaseWithOid(database_oid);

      // PrimaryKey Table
      oid_t PrimaryKeyTableId =
          db->GetTableWithName(constraint->pktable->relname)->GetOid();

      // Each table column names
      std::vector<std::string> pk_column_names;
      std::vector<std::string> fk_column_names;

      ListCell *column;
      if (constraint->pk_attrs != NULL && constraint->pk_attrs->length > 0) {
        foreach (column, constraint->pk_attrs) {
          char *attname = strVal(lfirst(column));
          pk_column_names.push_back(attname);
        }
      }
      if (constraint->fk_attrs != NULL && constraint->fk_attrs->length > 0) {
        foreach (column, constraint->fk_attrs) {
          char *attname = strVal(lfirst(column));
          fk_column_names.push_back(attname);
        }
      }

      catalog::ForeignKey *foreign_key = new catalog::ForeignKey(
          PrimaryKeyTableId, pk_column_names, fk_column_names,
          constraint->fk_upd_action, constraint->fk_del_action, conname);
      foreign_keys.push_back(*foreign_key);

    } break;
    default:
      LOG_WARN("Unrecognized constraint type %d\n", (int)contype);
      break;
  }

  // FIXME :
  bool status = SetReferenceTables(foreign_keys, relation_oid);
  if (status == false) {
    LOG_WARN("Failed to set reference tables");
  }

  return true;
}

/**
 * @brief Set Reference Tables
 * @param reference table namees  reference table names
 * @param relation_oid relation oid
 * @return true if we set the reference tables, false otherwise
 */
bool DDLTable::SetReferenceTables(
    std::vector<catalog::ForeignKey> &foreign_keys, oid_t relation_oid) {
  assert(relation_oid);
  oid_t database_oid = Bridge::GetCurrentDatabaseOid();
  assert(database_oid);

  storage::DataTable *current_table =
      (storage::DataTable *)catalog::Manager::GetInstance().GetTableWithOid(
          database_oid, relation_oid);

  for (auto foreign_key : foreign_keys) {
    current_table->AddForeignKey(&foreign_key);
  }

  return true;
}

}  // namespace bridge
}  // namespace peloton
<|endoftext|>


<commit_before>/*
    This file is part of Akregator.

    Copyright (C) 2007 Frank Osterfeld <osterfeld@kde.org>

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.

    As a special exception, permission is given to link this program
    with any edition of Qt, and distribute the resulting executable,
    without including the source code for Qt in the source distribution.
*/

#include "selectioncontroller.h"

#include "actionmanager.h"
#include "article.h"
#include "articlejobs.h"
#include "articlemodel.h"
#include "feedlist.h"
#include "subscriptionlistmodel.h"
#include "treenode.h"

#include <KRandom>
#include <KDebug>

#include <QAbstractItemView>
#include <QMenu>
#include <QTimer>

using namespace boost;
using namespace Akregator;

namespace {
    static Akregator::Article articleForIndex( const QModelIndex& index, FeedList* feedList )
    {
        if ( !index.isValid() )
            return Akregator::Article();

        const QString guid = index.data( ArticleModel::GuidRole ).toString();
        const QString feedId = index.data( ArticleModel::FeedIdRole ).toString();
        return feedList->findArticle( feedId, guid );
    }

    static QList<Akregator::Article> articlesForIndexes( const QModelIndexList& indexes, FeedList* feedList )
    {
        QList<Akregator::Article> articles;
        Q_FOREACH ( const QModelIndex& i, indexes )
        {
            const Article a = articleForIndex( i, feedList );
            if ( a.isNull() )
                continue;
            articles.append( articleForIndex( i, feedList ) );
        }

        return articles;
    }

    static Akregator::TreeNode* subscriptionForIndex( const QModelIndex& index, Akregator::FeedList* feedList )
    {
        if ( !index.isValid() )
            return 0L;

         return feedList->findByID( index.data( Akregator::SubscriptionListModel::SubscriptionIdRole ).toInt() );
    }
} // anon namespace

Akregator::SelectionController::SelectionController( QObject* parent )
    : AbstractSelectionController( parent ),
    m_feedList(),
    m_feedSelector(),
    m_articleLister( 0 ),
    m_singleDisplay( 0 ),
    m_subscriptionModel ( new SubscriptionListModel( shared_ptr<FeedList>(), this ) ),
    m_folderExpansionHandler( 0 ),
    m_articleModel( 0 ),
    m_selectedSubscription()
{
}

Akregator::SelectionController::~SelectionController()
{
    delete m_articleModel;
}

void Akregator::SelectionController::setFeedSelector( QAbstractItemView* feedSelector )
{
    if ( m_feedSelector == feedSelector )
        return;

    if ( m_feedSelector ) {
        m_feedSelector->disconnect( this );
        m_feedSelector->selectionModel()->disconnect( this );
    }

    m_feedSelector = feedSelector;

    if ( !m_feedSelector )
        return;

    m_feedSelector->setModel( m_subscriptionModel );

    connect( m_feedSelector, SIGNAL(customContextMenuRequested(QPoint)),
             this, SLOT(subscriptionContextMenuRequested(QPoint)) );
    connect( m_feedSelector->selectionModel(), SIGNAL(currentChanged(QModelIndex,QModelIndex)),
             this, SLOT(selectedSubscriptionChanged(QModelIndex)) );

}

void Akregator::SelectionController::setArticleLister( Akregator::ArticleLister* lister )
{
    if ( m_articleLister == lister )
        return;

    if ( m_articleLister )
        m_articleLister->articleSelectionModel()->disconnect( this );
    if ( m_articleLister && m_articleLister->itemView() )
        m_articleLister->itemView()->disconnect( this );

    m_articleLister = lister;

    if ( m_articleLister && m_articleLister->itemView() )
        connect( m_articleLister->itemView(), SIGNAL(doubleClicked(QModelIndex)),
                 this, SLOT(articleIndexDoubleClicked(QModelIndex))  );
}

void Akregator::SelectionController::setSingleArticleDisplay( Akregator::SingleArticleDisplay* display )
{
    m_singleDisplay = display;
}

Akregator::Article Akregator::SelectionController::currentArticle() const
{
    return ::articleForIndex( m_articleLister->articleSelectionModel()->currentIndex(), m_feedList.get() );
}

QList<Akregator::Article> Akregator::SelectionController::selectedArticles() const
{
    return ::articlesForIndexes( m_articleLister->articleSelectionModel()->selectedRows(), m_feedList.get() );
}

Akregator::TreeNode* Akregator::SelectionController::selectedSubscription() const
{
    return ::subscriptionForIndex( m_feedSelector->selectionModel()->currentIndex(), m_feedList.get() );
}

void Akregator::SelectionController::setFeedList( const shared_ptr<FeedList>& list )
{
    if ( m_feedList == list )
        return;

    m_feedList = list;
    std::auto_ptr<SubscriptionListModel> oldModel( m_subscriptionModel );
    m_subscriptionModel = new SubscriptionListModel( m_feedList, this );

    if ( m_folderExpansionHandler ) {
        m_folderExpansionHandler->setFeedList( m_feedList );
        m_folderExpansionHandler->setModel( m_subscriptionModel );
    }

    if ( m_feedSelector ) {
        m_feedSelector->setModel( m_subscriptionModel );
        disconnect( m_feedSelector->selectionModel(), SIGNAL(currentChanged(QModelIndex,QModelIndex)),
                    this, SLOT(selectedSubscriptionChanged(QModelIndex)) );
        connect( m_feedSelector->selectionModel(), SIGNAL(currentChanged(QModelIndex,QModelIndex)),
                 this, SLOT(selectedSubscriptionChanged(QModelIndex)) );
    }
}

void Akregator::SelectionController::setFolderExpansionHandler( Akregator::FolderExpansionHandler* handler )
{
    if ( handler == m_folderExpansionHandler )
        return;
    m_folderExpansionHandler = handler;
    if ( !m_folderExpansionHandler )
        return;
    handler->setFeedList( m_feedList );
    handler->setModel( m_subscriptionModel );
}

void Akregator::SelectionController::articleHeadersAvailable( KJob* job )
{
    assert( job );
    assert( job == m_listJob );

    if ( job->error() ) {
        kWarning() << job->errorText();
        return;
    }
    TreeNode* const node = m_listJob->node();

    assert( node ); // if there was no error, the node must still exist
    assert( node == m_selectedSubscription ); //...and equal the previously selected node

    ArticleModel* const newModel = new ArticleModel( m_listJob->articles() );

    connect( node, SIGNAL( destroyed() ),
             newModel, SLOT( clear() ) );
    connect( node, SIGNAL( signalArticlesAdded( Akregator::TreeNode*, QList<Akregator::Article> ) ),
            newModel, SLOT( articlesAdded( Akregator::TreeNode*, QList<Akregator::Article> ) ) );
    connect( node, SIGNAL( signalArticlesRemoved( Akregator::TreeNode*, QList<Akregator::Article> ) ),
             newModel, SLOT( articlesRemoved( Akregator::TreeNode*, QList<Akregator::Article> ) ) );
    connect( node, SIGNAL( signalArticlesUpdated( Akregator::TreeNode*, QList<Akregator::Article> ) ),
             newModel, SLOT( articlesUpdated( Akregator::TreeNode*, QList<Akregator::Article> ) ) );

    m_articleLister->setIsAggregation( node->isAggregation() );
    m_articleLister->setArticleModel( newModel );
    delete m_articleModel; //order is important: do not delete the old model before the new model is set in the view
    m_articleModel = newModel;

    disconnect( m_articleLister->articleSelectionModel(), SIGNAL(selectionChanged(QItemSelection, QItemSelection)),
                this, SLOT(articleSelectionChanged()) );
    connect( m_articleLister->articleSelectionModel(), SIGNAL(selectionChanged(QItemSelection, QItemSelection)),
             this, SLOT(articleSelectionChanged()) );

    if ( node )
        m_articleLister->setScrollBarPositions( node->listViewScrollBarPositions() );
}

void Akregator::SelectionController::selectedSubscriptionChanged( const QModelIndex& index )
{
    if ( !index.isValid() )
        return;

    if ( m_selectedSubscription && m_articleLister )
        m_selectedSubscription->setListViewScrollBarPositions( m_articleLister->scrollBarPositions() );

    m_selectedSubscription = selectedSubscription();
    emit currentSubscriptionChanged( m_selectedSubscription );

    // using a timer here internally to simulate async data fetching (which is still synchronous),
    // to ensure the UI copes with async behavior later on

    delete m_listJob;

    if ( !m_selectedSubscription )
        return;

    ArticleListJob* const job( new ArticleListJob( m_selectedSubscription ) );
    connect( job, SIGNAL(finished(KJob*)),
             this, SLOT(articleHeadersAvailable(KJob*)) );
    m_listJob = job;
    m_listJob->start();

}

void Akregator::SelectionController::subscriptionContextMenuRequested( const QPoint& point )
{
    Q_ASSERT( m_feedSelector );
    const TreeNode* const node = ::subscriptionForIndex( m_feedSelector->indexAt( point ), m_feedList.get() );
    if ( !node )
        return;

    QWidget* w = ActionManager::getInstance()->container( node->isGroup() ? "feedgroup_popup" : "feeds_popup" );
    QMenu* popup = qobject_cast<QMenu*>( w );
    if ( popup )
    {
        const QPoint globalPos = m_feedSelector->viewport()->mapToGlobal( point );
        popup->exec( globalPos );
    }
}

void Akregator::SelectionController::articleSelectionChanged()
{
    const Akregator::Article article = currentArticle();
    if ( m_singleDisplay )
        m_singleDisplay->showArticle( article );
    emit currentArticleChanged( article );
}

void Akregator::SelectionController::articleIndexDoubleClicked( const QModelIndex& index )
{
    const Akregator::Article article = ::articleForIndex( index, m_feedList.get() );
    emit articleDoubleClicked( article );
}

void SelectionController::setFilters( const std::vector<boost::shared_ptr<const Filters::AbstractMatcher> >& matchers )
{
    Q_ASSERT( m_articleLister );
    m_articleLister->setFilters( matchers );
}

void SelectionController::forceFilterUpdate()
{
    Q_ASSERT( m_articleLister );
    m_articleLister->forceFilterUpdate();
}

#include "selectioncontroller.moc"
<commit_msg>don't crash when no selection model exists (as there is no feed selected) BUG:224555<commit_after>/*
    This file is part of Akregator.

    Copyright (C) 2007 Frank Osterfeld <osterfeld@kde.org>

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.

    As a special exception, permission is given to link this program
    with any edition of Qt, and distribute the resulting executable,
    without including the source code for Qt in the source distribution.
*/

#include "selectioncontroller.h"

#include "actionmanager.h"
#include "article.h"
#include "articlejobs.h"
#include "articlemodel.h"
#include "feedlist.h"
#include "subscriptionlistmodel.h"
#include "treenode.h"

#include <KRandom>
#include <KDebug>

#include <QAbstractItemView>
#include <QMenu>
#include <QTimer>

using namespace boost;
using namespace Akregator;

namespace {
    static Akregator::Article articleForIndex( const QModelIndex& index, FeedList* feedList )
    {
        if ( !index.isValid() )
            return Akregator::Article();

        const QString guid = index.data( ArticleModel::GuidRole ).toString();
        const QString feedId = index.data( ArticleModel::FeedIdRole ).toString();
        return feedList->findArticle( feedId, guid );
    }

    static QList<Akregator::Article> articlesForIndexes( const QModelIndexList& indexes, FeedList* feedList )
    {
        QList<Akregator::Article> articles;
        Q_FOREACH ( const QModelIndex& i, indexes )
        {
            const Article a = articleForIndex( i, feedList );
            if ( a.isNull() )
                continue;
            articles.append( articleForIndex( i, feedList ) );
        }

        return articles;
    }

    static Akregator::TreeNode* subscriptionForIndex( const QModelIndex& index, Akregator::FeedList* feedList )
    {
        if ( !index.isValid() )
            return 0L;

         return feedList->findByID( index.data( Akregator::SubscriptionListModel::SubscriptionIdRole ).toInt() );
    }
} // anon namespace

Akregator::SelectionController::SelectionController( QObject* parent )
    : AbstractSelectionController( parent ),
    m_feedList(),
    m_feedSelector(),
    m_articleLister( 0 ),
    m_singleDisplay( 0 ),
    m_subscriptionModel ( new SubscriptionListModel( shared_ptr<FeedList>(), this ) ),
    m_folderExpansionHandler( 0 ),
    m_articleModel( 0 ),
    m_selectedSubscription()
{
}

Akregator::SelectionController::~SelectionController()
{
    delete m_articleModel;
}

void Akregator::SelectionController::setFeedSelector( QAbstractItemView* feedSelector )
{
    if ( m_feedSelector == feedSelector )
        return;

    if ( m_feedSelector ) {
        m_feedSelector->disconnect( this );
        m_feedSelector->selectionModel()->disconnect( this );
    }

    m_feedSelector = feedSelector;

    if ( !m_feedSelector )
        return;

    m_feedSelector->setModel( m_subscriptionModel );

    connect( m_feedSelector, SIGNAL(customContextMenuRequested(QPoint)),
             this, SLOT(subscriptionContextMenuRequested(QPoint)) );
    connect( m_feedSelector->selectionModel(), SIGNAL(currentChanged(QModelIndex,QModelIndex)),
             this, SLOT(selectedSubscriptionChanged(QModelIndex)) );

}

void Akregator::SelectionController::setArticleLister( Akregator::ArticleLister* lister )
{
    if ( m_articleLister == lister )
        return;

    if ( m_articleLister )
        m_articleLister->articleSelectionModel()->disconnect( this );
    if ( m_articleLister && m_articleLister->itemView() )
        m_articleLister->itemView()->disconnect( this );

    m_articleLister = lister;

    if ( m_articleLister && m_articleLister->itemView() )
        connect( m_articleLister->itemView(), SIGNAL(doubleClicked(QModelIndex)),
                 this, SLOT(articleIndexDoubleClicked(QModelIndex))  );
}

void Akregator::SelectionController::setSingleArticleDisplay( Akregator::SingleArticleDisplay* display )
{
    m_singleDisplay = display;
}

Akregator::Article Akregator::SelectionController::currentArticle() const
{
    if ( !m_articleLister || !m_articleLister->articleSelectionModel() )
        return Article();
    return ::articleForIndex( m_articleLister->articleSelectionModel()->currentIndex(), m_feedList.get() );
}

QList<Akregator::Article> Akregator::SelectionController::selectedArticles() const
{
    if ( !m_articleLister || !m_articleLister->articleSelectionModel() )
        return QList<Akregator::Article>();
    return ::articlesForIndexes( m_articleLister->articleSelectionModel()->selectedRows(), m_feedList.get() );
}

Akregator::TreeNode* Akregator::SelectionController::selectedSubscription() const
{
    return ::subscriptionForIndex( m_feedSelector->selectionModel()->currentIndex(), m_feedList.get() );
}

void Akregator::SelectionController::setFeedList( const shared_ptr<FeedList>& list )
{
    if ( m_feedList == list )
        return;

    m_feedList = list;
    std::auto_ptr<SubscriptionListModel> oldModel( m_subscriptionModel );
    m_subscriptionModel = new SubscriptionListModel( m_feedList, this );

    if ( m_folderExpansionHandler ) {
        m_folderExpansionHandler->setFeedList( m_feedList );
        m_folderExpansionHandler->setModel( m_subscriptionModel );
    }

    if ( m_feedSelector ) {
        m_feedSelector->setModel( m_subscriptionModel );
        disconnect( m_feedSelector->selectionModel(), SIGNAL(currentChanged(QModelIndex,QModelIndex)),
                    this, SLOT(selectedSubscriptionChanged(QModelIndex)) );
        connect( m_feedSelector->selectionModel(), SIGNAL(currentChanged(QModelIndex,QModelIndex)),
                 this, SLOT(selectedSubscriptionChanged(QModelIndex)) );
    }
}

void Akregator::SelectionController::setFolderExpansionHandler( Akregator::FolderExpansionHandler* handler )
{
    if ( handler == m_folderExpansionHandler )
        return;
    m_folderExpansionHandler = handler;
    if ( !m_folderExpansionHandler )
        return;
    handler->setFeedList( m_feedList );
    handler->setModel( m_subscriptionModel );
}

void Akregator::SelectionController::articleHeadersAvailable( KJob* job )
{
    assert( job );
    assert( job == m_listJob );

    if ( job->error() ) {
        kWarning() << job->errorText();
        return;
    }
    TreeNode* const node = m_listJob->node();

    assert( node ); // if there was no error, the node must still exist
    assert( node == m_selectedSubscription ); //...and equal the previously selected node

    ArticleModel* const newModel = new ArticleModel( m_listJob->articles() );

    connect( node, SIGNAL( destroyed() ),
             newModel, SLOT( clear() ) );
    connect( node, SIGNAL( signalArticlesAdded( Akregator::TreeNode*, QList<Akregator::Article> ) ),
            newModel, SLOT( articlesAdded( Akregator::TreeNode*, QList<Akregator::Article> ) ) );
    connect( node, SIGNAL( signalArticlesRemoved( Akregator::TreeNode*, QList<Akregator::Article> ) ),
             newModel, SLOT( articlesRemoved( Akregator::TreeNode*, QList<Akregator::Article> ) ) );
    connect( node, SIGNAL( signalArticlesUpdated( Akregator::TreeNode*, QList<Akregator::Article> ) ),
             newModel, SLOT( articlesUpdated( Akregator::TreeNode*, QList<Akregator::Article> ) ) );

    m_articleLister->setIsAggregation( node->isAggregation() );
    m_articleLister->setArticleModel( newModel );
    delete m_articleModel; //order is important: do not delete the old model before the new model is set in the view
    m_articleModel = newModel;

    disconnect( m_articleLister->articleSelectionModel(), SIGNAL(selectionChanged(QItemSelection, QItemSelection)),
                this, SLOT(articleSelectionChanged()) );
    connect( m_articleLister->articleSelectionModel(), SIGNAL(selectionChanged(QItemSelection, QItemSelection)),
             this, SLOT(articleSelectionChanged()) );

    if ( node )
        m_articleLister->setScrollBarPositions( node->listViewScrollBarPositions() );
}

void Akregator::SelectionController::selectedSubscriptionChanged( const QModelIndex& index )
{
    if ( !index.isValid() )
        return;

    if ( m_selectedSubscription && m_articleLister )
        m_selectedSubscription->setListViewScrollBarPositions( m_articleLister->scrollBarPositions() );

    m_selectedSubscription = selectedSubscription();
    emit currentSubscriptionChanged( m_selectedSubscription );

    // using a timer here internally to simulate async data fetching (which is still synchronous),
    // to ensure the UI copes with async behavior later on

    delete m_listJob;

    if ( !m_selectedSubscription )
        return;

    ArticleListJob* const job( new ArticleListJob( m_selectedSubscription ) );
    connect( job, SIGNAL(finished(KJob*)),
             this, SLOT(articleHeadersAvailable(KJob*)) );
    m_listJob = job;
    m_listJob->start();

}

void Akregator::SelectionController::subscriptionContextMenuRequested( const QPoint& point )
{
    Q_ASSERT( m_feedSelector );
    const TreeNode* const node = ::subscriptionForIndex( m_feedSelector->indexAt( point ), m_feedList.get() );
    if ( !node )
        return;

    QWidget* w = ActionManager::getInstance()->container( node->isGroup() ? "feedgroup_popup" : "feeds_popup" );
    QMenu* popup = qobject_cast<QMenu*>( w );
    if ( popup )
    {
        const QPoint globalPos = m_feedSelector->viewport()->mapToGlobal( point );
        popup->exec( globalPos );
    }
}

void Akregator::SelectionController::articleSelectionChanged()
{
    const Akregator::Article article = currentArticle();
    if ( m_singleDisplay )
        m_singleDisplay->showArticle( article );
    emit currentArticleChanged( article );
}

void Akregator::SelectionController::articleIndexDoubleClicked( const QModelIndex& index )
{
    const Akregator::Article article = ::articleForIndex( index, m_feedList.get() );
    emit articleDoubleClicked( article );
}

void SelectionController::setFilters( const std::vector<boost::shared_ptr<const Filters::AbstractMatcher> >& matchers )
{
    Q_ASSERT( m_articleLister );
    m_articleLister->setFilters( matchers );
}

void SelectionController::forceFilterUpdate()
{
    Q_ASSERT( m_articleLister );
    m_articleLister->forceFilterUpdate();
}

#include "selectioncontroller.moc"
<|endoftext|>


<commit_before>#include <sys/errno.h>
#include <sys/uio.h>
#include <errno.h>
#include <limits.h>
#include <unistd.h>

#include <common/buffer.h>

#include <event/action.h>
#include <event/callback.h>
#include <event/event_system.h>

#include <io/io_system.h>

#define	IO_READ_BUFFER_SIZE	65536

IOSystem::Handle::Handle(int fd, Channel *owner)
: log_("/io/system/handle"),
  fd_(fd),
  owner_(owner),
  close_callback_(NULL),
  close_action_(NULL),
  read_amount_(0),
  read_buffer_(),
  read_callback_(NULL),
  read_action_(NULL),
  write_buffer_(),
  write_callback_(NULL),
  write_action_(NULL)
{ }

IOSystem::Handle::~Handle()
{
	ASSERT(fd_ == -1);

	ASSERT(close_action_ == NULL);
	ASSERT(close_callback_ == NULL);

	ASSERT(read_action_ == NULL);
	ASSERT(read_callback_ == NULL);

	ASSERT(write_action_ == NULL);
	ASSERT(write_callback_ == NULL);
}

void
IOSystem::Handle::close_callback(void)
{
	close_action_->cancel();
	close_action_ = NULL;

	ASSERT(fd_ != -1);
	int rv = ::close(fd_);
	if (rv == -1) {
		switch (errno) {
		case EAGAIN:
			close_action_ = close_schedule();
			break;
		default:
			close_callback_->event(Event(Event::Error, errno));
			Action *a = EventSystem::instance()->schedule(close_callback_);
			close_action_ = a;
			close_callback_ = NULL;
			break;
		}
		return;
	}
	fd_ = -1;
	close_callback_->event(Event(Event::Done, 0));
	Action *a = EventSystem::instance()->schedule(close_callback_);
	close_action_ = a;
	close_callback_ = NULL;
}

void
IOSystem::Handle::close_cancel(void)
{
	ASSERT(close_action_ != NULL);
	close_action_->cancel();
	close_action_ = NULL;

	if (close_callback_ != NULL) {
		delete close_callback_;
		close_callback_ = NULL;
	}
}

Action *
IOSystem::Handle::close_schedule(void)
{
	ASSERT(close_action_ == NULL);
	Callback *cb = callback(this, &IOSystem::Handle::close_callback);
	Action *a = EventSystem::instance()->schedule(cb);
	return (a);
}

void
IOSystem::Handle::read_callback(Event e)
{
	read_action_->cancel();
	read_action_ = NULL;

	switch (e.type_) {
	case Event::EOS:
	case Event::Done:
		break;
	default:
		HALT(log_) << "Unexpected event: " << e;
	}

	size_t rlen;

	if (read_amount_ == 0)
		rlen = IO_READ_BUFFER_SIZE;
	else {
		rlen = read_amount_ - read_buffer_.length();
		ASSERT(rlen != 0);
		if (rlen > IO_READ_BUFFER_SIZE)
			rlen = IO_READ_BUFFER_SIZE;
	}

	uint8_t data[IO_READ_BUFFER_SIZE];
	ssize_t len = ::read(fd_, data, sizeof data);
	if (len == -1) {
		switch (errno) {
		case EAGAIN:
			read_action_ = read_schedule();
			break;
		default:
			read_callback_->event(Event(Event::Error, errno, read_buffer_));
			Action *a = EventSystem::instance()->schedule(read_callback_);
			read_action_ = a;
			read_callback_ = NULL;
			read_buffer_.clear();
			read_amount_ = 0;
			break;
		}
		return;
	}

	/*
	 * XXX
	 * If we get an EOS from EventPoll, should we just terminate after that
	 * one read?  How can we tell how much data is still available to be
	 * read?  Will we get it all in one read?  Eventually, one would expect
	 * to get a read with a length of 0, or an error, but will kevent even
	 * continue to fire off read events?
	 */
	if (len == 0) {
		read_callback_->event(Event(Event::EOS, 0, read_buffer_));
		Action *a = EventSystem::instance()->schedule(read_callback_);
		read_action_ = a;
		read_callback_ = NULL;
		read_buffer_.clear();
		read_amount_ = 0;
		return;
	}

	read_buffer_.append(data, len);
	read_action_ = read_schedule();
}

void
IOSystem::Handle::read_cancel(void)
{
	ASSERT(read_action_ != NULL);
	read_action_->cancel();
	read_action_ = NULL;

	if (read_callback_ != NULL) {
		delete read_callback_;
		read_callback_ = NULL;
	}
}

Action *
IOSystem::Handle::read_schedule(void)
{
	ASSERT(read_action_ == NULL);

	if (!read_buffer_.empty() && read_buffer_.length() >= read_amount_) {
		if (read_amount_ == 0)
			read_amount_ = read_buffer_.length();
		read_callback_->event(Event(Event::Done, 0, Buffer(read_buffer_, read_amount_)));
		Action *a = EventSystem::instance()->schedule(read_callback_);
		read_callback_ = NULL;
		read_buffer_.skip(read_amount_);
		read_amount_ = 0;
		return (a);
	}

	EventCallback *cb = callback(this, &IOSystem::Handle::read_callback);
	Action *a = EventSystem::instance()->poll(EventPoll::Readable, fd_, cb);
	return (a);
}

void
IOSystem::Handle::write_callback(Event e)
{
	write_action_->cancel();
	write_action_ = NULL;

	switch (e.type_) {
	case Event::EOS:
	case Event::Done:
		break;
	default:
		HALT(log_) << "Unexpected event: " << e;
	}

	/* XXX This doesn't handle UDP nicely.  Right?  */
	struct iovec iov[IOV_MAX];
	size_t iovcnt = write_buffer_.fill_iovec(iov, IOV_MAX);

	ssize_t len = ::writev(fd_, iov, iovcnt);
	if (len == -1) {
		switch (errno) {
		case EAGAIN:
			write_action_ = write_schedule();
			break;
		default:
			write_callback_->event(Event(Event::Error, errno));
			Action *a = EventSystem::instance()->schedule(write_callback_);
			write_action_ = a;
			write_callback_ = NULL;
			break;
		}
		return;
	}

	write_buffer_.skip(len);

	if (write_buffer_.empty()) {
		write_callback_->event(Event(Event::Done, 0));
		Action *a = EventSystem::instance()->schedule(write_callback_);
		write_action_ = a;
		write_callback_ = NULL;
		return;
	}
	write_action_ = write_schedule();
}

void
IOSystem::Handle::write_cancel(void)
{
	ASSERT(write_action_ != NULL);
	write_action_->cancel();
	write_action_ = NULL;

	if (write_callback_ != NULL) {
		delete write_callback_;
		write_callback_ = NULL;
	}
}

Action *
IOSystem::Handle::write_schedule(void)
{
	ASSERT(write_action_ == NULL);
	EventCallback *cb = callback(this, &IOSystem::Handle::write_callback);
	Action *a = EventSystem::instance()->poll(EventPoll::Writable, fd_, cb);
	return (a);
}

IOSystem::IOSystem(void)
: log_("/io/system"),
  handle_map_()
{ }

IOSystem::~IOSystem()
{
	ASSERT(handle_map_.empty());
}

void
IOSystem::attach(int fd, Channel *owner)
{
	ASSERT(handle_map_.find(handle_key_t(fd, owner)) == handle_map_.end());
	handle_map_[handle_key_t(fd, owner)] = new IOSystem::Handle(fd, owner);
}

void
IOSystem::detach(int fd, Channel *owner)
{
	handle_map_t::iterator it;
	IOSystem::Handle *h;

	it = handle_map_.find(handle_key_t(fd, owner));
	ASSERT(it != handle_map_.end());

	h = it->second;
	ASSERT(h != NULL);
	ASSERT(h->owner_ == owner);

	handle_map_.erase(it);
	delete h;
}

Action *
IOSystem::close(int fd, Channel *owner, EventCallback *cb)
{
	IOSystem::Handle *h;

	h = handle_map_[handle_key_t(fd, owner)];
	ASSERT(h != NULL);

	ASSERT(h->close_callback_ == NULL);
	ASSERT(h->close_action_ == NULL);

	ASSERT(h->read_callback_ == NULL);
	ASSERT(h->read_action_ == NULL);

	ASSERT(h->write_callback_ == NULL);
	ASSERT(h->write_action_ == NULL);

	ASSERT(h->fd_ != -1);

	h->close_callback_ = cb;
	h->close_action_ = h->close_schedule();
	return (cancellation(h, &IOSystem::Handle::close_cancel));
}

Action *
IOSystem::read(int fd, Channel *owner, size_t amount, EventCallback *cb)
{
	IOSystem::Handle *h;

	h = handle_map_[handle_key_t(fd, owner)];
	ASSERT(h != NULL);

	ASSERT(h->read_callback_ == NULL);
	ASSERT(h->read_action_ == NULL);

	h->read_amount_ = amount;
	h->read_callback_ = cb;
	h->read_action_ = h->read_schedule();
	return (cancellation(h, &IOSystem::Handle::read_cancel));
}

Action *
IOSystem::write(int fd, Channel *owner, Buffer *buffer, EventCallback *cb)
{
	IOSystem::Handle *h;

	h = handle_map_[handle_key_t(fd, owner)];
	ASSERT(h != NULL);

	ASSERT(h->write_callback_ == NULL);
	ASSERT(h->write_action_ == NULL);
	ASSERT(h->write_buffer_.empty());

	ASSERT(!buffer->empty());
	h->write_buffer_.append(buffer);
	buffer->clear();

	h->write_callback_ = cb;
	h->write_action_ = h->write_schedule();
	return (cancellation(h, &IOSystem::Handle::write_cancel));
}
<commit_msg>Note my unhappiness with doing an extra copy of read data.<commit_after>#include <sys/errno.h>
#include <sys/uio.h>
#include <errno.h>
#include <limits.h>
#include <unistd.h>

#include <common/buffer.h>

#include <event/action.h>
#include <event/callback.h>
#include <event/event_system.h>

#include <io/io_system.h>

#define	IO_READ_BUFFER_SIZE	65536

IOSystem::Handle::Handle(int fd, Channel *owner)
: log_("/io/system/handle"),
  fd_(fd),
  owner_(owner),
  close_callback_(NULL),
  close_action_(NULL),
  read_amount_(0),
  read_buffer_(),
  read_callback_(NULL),
  read_action_(NULL),
  write_buffer_(),
  write_callback_(NULL),
  write_action_(NULL)
{ }

IOSystem::Handle::~Handle()
{
	ASSERT(fd_ == -1);

	ASSERT(close_action_ == NULL);
	ASSERT(close_callback_ == NULL);

	ASSERT(read_action_ == NULL);
	ASSERT(read_callback_ == NULL);

	ASSERT(write_action_ == NULL);
	ASSERT(write_callback_ == NULL);
}

void
IOSystem::Handle::close_callback(void)
{
	close_action_->cancel();
	close_action_ = NULL;

	ASSERT(fd_ != -1);
	int rv = ::close(fd_);
	if (rv == -1) {
		switch (errno) {
		case EAGAIN:
			close_action_ = close_schedule();
			break;
		default:
			close_callback_->event(Event(Event::Error, errno));
			Action *a = EventSystem::instance()->schedule(close_callback_);
			close_action_ = a;
			close_callback_ = NULL;
			break;
		}
		return;
	}
	fd_ = -1;
	close_callback_->event(Event(Event::Done, 0));
	Action *a = EventSystem::instance()->schedule(close_callback_);
	close_action_ = a;
	close_callback_ = NULL;
}

void
IOSystem::Handle::close_cancel(void)
{
	ASSERT(close_action_ != NULL);
	close_action_->cancel();
	close_action_ = NULL;

	if (close_callback_ != NULL) {
		delete close_callback_;
		close_callback_ = NULL;
	}
}

Action *
IOSystem::Handle::close_schedule(void)
{
	ASSERT(close_action_ == NULL);
	Callback *cb = callback(this, &IOSystem::Handle::close_callback);
	Action *a = EventSystem::instance()->schedule(cb);
	return (a);
}

void
IOSystem::Handle::read_callback(Event e)
{
	read_action_->cancel();
	read_action_ = NULL;

	switch (e.type_) {
	case Event::EOS:
	case Event::Done:
		break;
	default:
		HALT(log_) << "Unexpected event: " << e;
	}

	size_t rlen;

	if (read_amount_ == 0)
		rlen = IO_READ_BUFFER_SIZE;
	else {
		rlen = read_amount_ - read_buffer_.length();
		ASSERT(rlen != 0);
		if (rlen > IO_READ_BUFFER_SIZE)
			rlen = IO_READ_BUFFER_SIZE;
	}

	/* XXX Read into an iovec of BufferSegments?  */
	uint8_t data[IO_READ_BUFFER_SIZE];
	ssize_t len = ::read(fd_, data, sizeof data);
	if (len == -1) {
		switch (errno) {
		case EAGAIN:
			read_action_ = read_schedule();
			break;
		default:
			read_callback_->event(Event(Event::Error, errno, read_buffer_));
			Action *a = EventSystem::instance()->schedule(read_callback_);
			read_action_ = a;
			read_callback_ = NULL;
			read_buffer_.clear();
			read_amount_ = 0;
			break;
		}
		return;
	}

	/*
	 * XXX
	 * If we get an EOS from EventPoll, should we just terminate after that
	 * one read?  How can we tell how much data is still available to be
	 * read?  Will we get it all in one read?  Eventually, one would expect
	 * to get a read with a length of 0, or an error, but will kevent even
	 * continue to fire off read events?
	 */
	if (len == 0) {
		read_callback_->event(Event(Event::EOS, 0, read_buffer_));
		Action *a = EventSystem::instance()->schedule(read_callback_);
		read_action_ = a;
		read_callback_ = NULL;
		read_buffer_.clear();
		read_amount_ = 0;
		return;
	}

	read_buffer_.append(data, len);
	read_action_ = read_schedule();
}

void
IOSystem::Handle::read_cancel(void)
{
	ASSERT(read_action_ != NULL);
	read_action_->cancel();
	read_action_ = NULL;

	if (read_callback_ != NULL) {
		delete read_callback_;
		read_callback_ = NULL;
	}
}

Action *
IOSystem::Handle::read_schedule(void)
{
	ASSERT(read_action_ == NULL);

	if (!read_buffer_.empty() && read_buffer_.length() >= read_amount_) {
		if (read_amount_ == 0)
			read_amount_ = read_buffer_.length();
		read_callback_->event(Event(Event::Done, 0, Buffer(read_buffer_, read_amount_)));
		Action *a = EventSystem::instance()->schedule(read_callback_);
		read_callback_ = NULL;
		read_buffer_.skip(read_amount_);
		read_amount_ = 0;
		return (a);
	}

	EventCallback *cb = callback(this, &IOSystem::Handle::read_callback);
	Action *a = EventSystem::instance()->poll(EventPoll::Readable, fd_, cb);
	return (a);
}

void
IOSystem::Handle::write_callback(Event e)
{
	write_action_->cancel();
	write_action_ = NULL;

	switch (e.type_) {
	case Event::EOS:
	case Event::Done:
		break;
	default:
		HALT(log_) << "Unexpected event: " << e;
	}

	/* XXX This doesn't handle UDP nicely.  Right?  */
	struct iovec iov[IOV_MAX];
	size_t iovcnt = write_buffer_.fill_iovec(iov, IOV_MAX);

	ssize_t len = ::writev(fd_, iov, iovcnt);
	if (len == -1) {
		switch (errno) {
		case EAGAIN:
			write_action_ = write_schedule();
			break;
		default:
			write_callback_->event(Event(Event::Error, errno));
			Action *a = EventSystem::instance()->schedule(write_callback_);
			write_action_ = a;
			write_callback_ = NULL;
			break;
		}
		return;
	}

	write_buffer_.skip(len);

	if (write_buffer_.empty()) {
		write_callback_->event(Event(Event::Done, 0));
		Action *a = EventSystem::instance()->schedule(write_callback_);
		write_action_ = a;
		write_callback_ = NULL;
		return;
	}
	write_action_ = write_schedule();
}

void
IOSystem::Handle::write_cancel(void)
{
	ASSERT(write_action_ != NULL);
	write_action_->cancel();
	write_action_ = NULL;

	if (write_callback_ != NULL) {
		delete write_callback_;
		write_callback_ = NULL;
	}
}

Action *
IOSystem::Handle::write_schedule(void)
{
	ASSERT(write_action_ == NULL);
	EventCallback *cb = callback(this, &IOSystem::Handle::write_callback);
	Action *a = EventSystem::instance()->poll(EventPoll::Writable, fd_, cb);
	return (a);
}

IOSystem::IOSystem(void)
: log_("/io/system"),
  handle_map_()
{ }

IOSystem::~IOSystem()
{
	ASSERT(handle_map_.empty());
}

void
IOSystem::attach(int fd, Channel *owner)
{
	ASSERT(handle_map_.find(handle_key_t(fd, owner)) == handle_map_.end());
	handle_map_[handle_key_t(fd, owner)] = new IOSystem::Handle(fd, owner);
}

void
IOSystem::detach(int fd, Channel *owner)
{
	handle_map_t::iterator it;
	IOSystem::Handle *h;

	it = handle_map_.find(handle_key_t(fd, owner));
	ASSERT(it != handle_map_.end());

	h = it->second;
	ASSERT(h != NULL);
	ASSERT(h->owner_ == owner);

	handle_map_.erase(it);
	delete h;
}

Action *
IOSystem::close(int fd, Channel *owner, EventCallback *cb)
{
	IOSystem::Handle *h;

	h = handle_map_[handle_key_t(fd, owner)];
	ASSERT(h != NULL);

	ASSERT(h->close_callback_ == NULL);
	ASSERT(h->close_action_ == NULL);

	ASSERT(h->read_callback_ == NULL);
	ASSERT(h->read_action_ == NULL);

	ASSERT(h->write_callback_ == NULL);
	ASSERT(h->write_action_ == NULL);

	ASSERT(h->fd_ != -1);

	h->close_callback_ = cb;
	h->close_action_ = h->close_schedule();
	return (cancellation(h, &IOSystem::Handle::close_cancel));
}

Action *
IOSystem::read(int fd, Channel *owner, size_t amount, EventCallback *cb)
{
	IOSystem::Handle *h;

	h = handle_map_[handle_key_t(fd, owner)];
	ASSERT(h != NULL);

	ASSERT(h->read_callback_ == NULL);
	ASSERT(h->read_action_ == NULL);

	h->read_amount_ = amount;
	h->read_callback_ = cb;
	h->read_action_ = h->read_schedule();
	return (cancellation(h, &IOSystem::Handle::read_cancel));
}

Action *
IOSystem::write(int fd, Channel *owner, Buffer *buffer, EventCallback *cb)
{
	IOSystem::Handle *h;

	h = handle_map_[handle_key_t(fd, owner)];
	ASSERT(h != NULL);

	ASSERT(h->write_callback_ == NULL);
	ASSERT(h->write_action_ == NULL);
	ASSERT(h->write_buffer_.empty());

	ASSERT(!buffer->empty());
	h->write_buffer_.append(buffer);
	buffer->clear();

	h->write_callback_ = cb;
	h->write_action_ = h->write_schedule();
	return (cancellation(h, &IOSystem::Handle::write_cancel));
}
<|endoftext|>