<commit_before>/* This file is part of Strigi Desktop Search
 *
 * Copyright (C) 2007 Jos van den Oever <jos@vandenoever.info>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library 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
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public License
 * along with this library; see the file COPYING.LIB.  If not, write to
 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA 02110-1301, USA.
 */
#include "diranalyzer.h"
#include "indexwriter.h"
#include "indexmanager.h"
#include "indexreader.h"
#include "filelister.h"
#include "analysisresult.h"
#include "analyzerconfiguration.h"
#include "strigi_thread.h"
#include "fileinputstream.h"
#include <map>
#include <iostream>
#include <sys/stat.h>

using namespace Strigi;
using namespace std;

class DirAnalyzer::Private {
public:
    DirLister dirlister;
    IndexManager& manager;
    AnalyzerConfiguration& config;
    StreamAnalyzer analyzer;
    AnalysisCaller* caller;

    Private(IndexManager& m, AnalyzerConfiguration& c)
            :dirlister(&c), manager(m), config(c), analyzer(c) {
        analyzer.setIndexWriter(*manager.indexWriter());
    }
    ~Private() {
    }
    int analyzeDir(const string& dir, int nthreads, AnalysisCaller* caller,
        const string& lastToSkip);
    int updateDirs(const vector<string>& dir, int nthreads,
        AnalysisCaller* caller);
    void analyze(StreamAnalyzer*);
    void update(StreamAnalyzer*);
    int analyzeFile(const string& path, time_t mtime, bool realfile);
};

struct DA {
    StreamAnalyzer* streamanalyzer;
    DirAnalyzer::Private* diranalyzer;
};

extern "C" // Linkage for functions passed to pthread_create matters
{
void*
analyzeInThread(void* d) {
    DA* a = static_cast<DA*>(d);
    a->diranalyzer->analyze(a->streamanalyzer);
    delete a;
    STRIGI_THREAD_EXIT(0);
    return 0; // Return bogus value
}
void*
updateInThread(void* d) {
    DA* a = static_cast<DA*>(d);
    a->diranalyzer->update(a->streamanalyzer);
    delete a;
    STRIGI_THREAD_EXIT(0);
    return 0; // Return bogus value
}
}

DirAnalyzer::DirAnalyzer(IndexManager& manager, AnalyzerConfiguration& conf)
        :p(new Private(manager, conf)) {
}
DirAnalyzer::~DirAnalyzer() {
    delete p;
}
int
DirAnalyzer::Private::analyzeFile(const string& path, time_t mtime,
        bool realfile) {
    AnalysisResult analysisresult(path, mtime, *manager.indexWriter(),
        analyzer, "");
    if (realfile) {
        FileInputStream file(path.c_str());
        return analysisresult.index(&file);
    } else {
        return analysisresult.index(0);
    }
}
void
DirAnalyzer::Private::analyze(StreamAnalyzer* analyzer) {
    IndexWriter& indexWriter = *manager.indexWriter();
    try {
        string parentpath;
        vector<pair<string, struct stat> > dirfiles;
        int r = dirlister.nextDir(parentpath, dirfiles);

        while (r == 0 && (caller == 0 || caller->continueAnalysis())) {
            vector<pair<string, struct stat> >::const_iterator end
                = dirfiles.end();
            for (vector<pair<string, struct stat> >::const_iterator i
                    = dirfiles.begin(); i != end; ++i) {
                const string& filepath(i->first);
                struct stat s = i->second;
                AnalysisResult analysisresult(filepath, s.st_mtime,
                    indexWriter, *analyzer, parentpath);
                if (S_ISREG(s.st_mode)) {
                    FileInputStream file(filepath.c_str());
                    analysisresult.index(&file);
                } else {
                    analysisresult.index(0);
                }
                if (!config.indexMore()) return;
            }
            r = dirlister.nextDir(parentpath, dirfiles);
        }
    } catch(...) {
        fprintf(stderr, "Unknown error\n");
    }
}
void
DirAnalyzer::Private::update(StreamAnalyzer* analyzer) {
    IndexReader* reader = manager.indexReader();
    vector<pair<string, struct stat> > dirfiles;
    map<string, time_t> dbdirfiles;
    vector<string> toDelete;
    vector<pair<string, struct stat> > toIndex;
    try {
        string path;
        // loop over all files that exist in the index
        int r = dirlister.nextDir(path, dirfiles);
        while (r >= 0 && (caller == 0 || caller->continueAnalysis())) {
            if (r < 0) {
                continue;
            }
            // get the files that are in the current database
            reader->getChildren(path, dbdirfiles);

            // get all files in this directory
            vector<pair<string, struct stat> >::const_iterator end
                = dirfiles.end();
            map<string, time_t>::const_iterator dbend = dbdirfiles.end();
            for (vector<pair<string, struct stat> >::const_iterator i
                    = dirfiles.begin(); i != end; ++i) {
                const string& filepath(i->first);
                time_t mtime = i->second.st_mtime;

                // check if this file is new or not
                map<string, time_t>::iterator j = dbdirfiles.find(filepath);
                bool newfile = j == dbend;
                bool updatedfile = !newfile && j->second != mtime;

                if (newfile || (updatedfile && !S_ISDIR(i->second.st_mode))) {
                    // if the file has not yet been indexed or if the mtime has
                    // changed, index it
                    // if a directory has been updated, this will not change the index
                    // so the entry is not removed from the index, nor reindexed
                    toIndex.push_back(make_pair(filepath, i->second));
                } else {
                    // files left in dbdirfiles after this loop will be deleted from the
                    // index. because this file has not changed, it should not be
                    // removed from the index
                    dbdirfiles.erase(j);
                }
            }
            // all the files left in dbdirfiles, are not in the current
            // directory and should be deleted
            for (map<string, time_t>::const_iterator i = dbdirfiles.begin();
                    i != dbend; ++i) {
                toDelete.push_back(i->first);
            }
            if (toDelete.size() > 0) {
                manager.indexWriter()->deleteEntries(toDelete);
            }
            vector<pair<string, struct stat> >::const_iterator fend
                = toIndex.end();
            for (vector<pair<string, struct stat> >::const_iterator i
                    = toIndex.begin(); i != fend; ++i) {
                AnalysisResult analysisresult(i->first, i->second.st_mtime,
                    *manager.indexWriter(), *analyzer, path);
                if (S_ISREG(i->second.st_mode)) {
                    FileInputStream file(i->first.c_str());
                    analysisresult.index(&file);
                } else {
                    analysisresult.index(0);
                }
            }
            toDelete.clear();
            toIndex.clear();
            r = dirlister.nextDir(path, dirfiles);
        }
    } catch(...) {
        fprintf(stderr, "Unknown error\n");
    }
}
int
DirAnalyzer::analyzeDir(const string& dir, int nthreads, AnalysisCaller* c,
        const string& lastToSkip) {
    return p->analyzeDir(dir, nthreads, c, lastToSkip);
}
namespace {
string
removeTrailingSlash(const string& path) {
    string cleanpath(path);
    if (path.length() && path[path.length()-1] == '/') {
        cleanpath.resize(path.length()-1);
    }
    return cleanpath;
}
}
int
DirAnalyzer::Private::analyzeDir(const string& dir, int nthreads,
        AnalysisCaller* c, const string& lastToSkip) {
    caller = c;
    // check if the path exists and if it is a file or a directory
    struct stat s;
    const string path(removeTrailingSlash(dir));
    int retval;
    if (path.size() == 0) {
        // special case for analyzing the root directory '/' on unix
        retval = stat("/", &s);
    } else {
        retval = stat(path.c_str(), &s);
    }
    time_t mtime = (retval == -1) ?0 :s.st_mtime;
    retval = analyzeFile(path, mtime, S_ISREG(s.st_mode));
    // if the path does not point to a directory, return
    if (!S_ISDIR(s.st_mode)) {
        manager.indexWriter()->commit();
        return retval;
    }
    dirlister.startListing(path);
    if (lastToSkip.length()) {
        dirlister.skipTillAfter(lastToSkip);
    }

    if (nthreads < 1) nthreads = 1;
    vector<StreamAnalyzer*> analyzers(nthreads);
    analyzers[0] = &analyzer;
    for (int i=1; i<nthreads; ++i) {
        analyzers[i] = new StreamAnalyzer(config);
        analyzers[i]->setIndexWriter(*manager.indexWriter());
    }
    vector<STRIGI_THREAD_TYPE> threads;
    threads.resize(nthreads-1);
    for (int i=1; i<nthreads; i++) {
        DA* da = new DA();
        da->diranalyzer = this;
        da->streamanalyzer = analyzers[i];
        STRIGI_THREAD_CREATE(&threads[i-1], analyzeInThread, da);
    }
    analyze(analyzers[0]);
    for (int i=1; i<nthreads; i++) {
        STRIGI_THREAD_JOIN(threads[i-1]);
        delete analyzers[i];
    }
    manager.indexWriter()->commit();
    return 0;
}
int
DirAnalyzer::updateDir(const string& dir, int nthreads, AnalysisCaller* caller){
    vector<string> dirs;
    dirs.push_back(dir);
    return p->updateDirs(dirs, nthreads, caller);
}
int
DirAnalyzer::Private::updateDirs(const vector<string>& dirs, int nthreads,
        AnalysisCaller* c) {
    IndexReader* reader = manager.indexReader();
    if (reader == 0) return -1;
    caller = c;

    // create the streamanalyzers
    if (nthreads < 1) nthreads = 1;
    vector<StreamAnalyzer*> analyzers(nthreads);
    analyzers[0] = &analyzer;
    for (int i=1; i<nthreads; ++i) {
        analyzers[i] = new StreamAnalyzer(config);
        analyzers[i]->setIndexWriter(*manager.indexWriter());
    }
    vector<STRIGI_THREAD_TYPE> threads;
    threads.resize(nthreads-1);

    // loop over all directories that should be updated
    for (vector<string>::const_iterator d =dirs.begin(); d != dirs.end(); ++d) {
        dirlister.startListing(removeTrailingSlash(*d));
        for (int i=1; i<nthreads; i++) {
            DA* da = new DA();
            da->diranalyzer = this;
            da->streamanalyzer = analyzers[i];
            STRIGI_THREAD_CREATE(&threads[i-1], updateInThread, da);
        }
        update(analyzers[0]);
        // wait until all threads have finished
        for (int i=1; i<nthreads; i++) {
            STRIGI_THREAD_JOIN(threads[i-1]);
        }
        dirlister.stopListing();
    }
    // clean up the analyzers
    for (int i=1; i<nthreads; i++) {
        delete analyzers[i];
    }

    // remove the files that were not encountered from the index
/*    vector<string> todelete(1);
    map<string,time_t>::iterator it = dbfiles.begin();
    while (it != dbfiles.end()) {
        todelete[0].assign(it->first);
        manager.indexWriter()->deleteEntries(todelete);
        ++it;
    }
    dbfiles.clear();*/
    manager.indexWriter()->commit();

    return 0;
}
int
DirAnalyzer::updateDirs(const vector<string>& dirs, int nthreads,
        AnalysisCaller* caller) {
    return p->updateDirs(dirs, nthreads, caller);
}
<commit_msg>Fix uninitialized values for non-existant file.<commit_after>/* This file is part of Strigi Desktop Search
 *
 * Copyright (C) 2007 Jos van den Oever <jos@vandenoever.info>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library 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
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public License
 * along with this library; see the file COPYING.LIB.  If not, write to
 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA 02110-1301, USA.
 */
#include "diranalyzer.h"
#include "indexwriter.h"
#include "indexmanager.h"
#include "indexreader.h"
#include "filelister.h"
#include "analysisresult.h"
#include "analyzerconfiguration.h"
#include "strigi_thread.h"
#include "fileinputstream.h"
#include <map>
#include <iostream>
#include <sys/stat.h>

using namespace Strigi;
using namespace std;

class DirAnalyzer::Private {
public:
    DirLister dirlister;
    IndexManager& manager;
    AnalyzerConfiguration& config;
    StreamAnalyzer analyzer;
    AnalysisCaller* caller;

    Private(IndexManager& m, AnalyzerConfiguration& c)
            :dirlister(&c), manager(m), config(c), analyzer(c) {
        analyzer.setIndexWriter(*manager.indexWriter());
    }
    ~Private() {
    }
    int analyzeDir(const string& dir, int nthreads, AnalysisCaller* caller,
        const string& lastToSkip);
    int updateDirs(const vector<string>& dir, int nthreads,
        AnalysisCaller* caller);
    void analyze(StreamAnalyzer*);
    void update(StreamAnalyzer*);
    int analyzeFile(const string& path, time_t mtime, bool realfile);
};

struct DA {
    StreamAnalyzer* streamanalyzer;
    DirAnalyzer::Private* diranalyzer;
};

extern "C" // Linkage for functions passed to pthread_create matters
{
void*
analyzeInThread(void* d) {
    DA* a = static_cast<DA*>(d);
    a->diranalyzer->analyze(a->streamanalyzer);
    delete a;
    STRIGI_THREAD_EXIT(0);
    return 0; // Return bogus value
}
void*
updateInThread(void* d) {
    DA* a = static_cast<DA*>(d);
    a->diranalyzer->update(a->streamanalyzer);
    delete a;
    STRIGI_THREAD_EXIT(0);
    return 0; // Return bogus value
}
}

DirAnalyzer::DirAnalyzer(IndexManager& manager, AnalyzerConfiguration& conf)
        :p(new Private(manager, conf)) {
}
DirAnalyzer::~DirAnalyzer() {
    delete p;
}
int
DirAnalyzer::Private::analyzeFile(const string& path, time_t mtime,
        bool realfile) {
    AnalysisResult analysisresult(path, mtime, *manager.indexWriter(),
        analyzer, "");
    if (realfile) {
        FileInputStream file(path.c_str());
        return analysisresult.index(&file);
    } else {
        return analysisresult.index(0);
    }
}
void
DirAnalyzer::Private::analyze(StreamAnalyzer* analyzer) {
    IndexWriter& indexWriter = *manager.indexWriter();
    try {
        string parentpath;
        vector<pair<string, struct stat> > dirfiles;
        int r = dirlister.nextDir(parentpath, dirfiles);

        while (r == 0 && (caller == 0 || caller->continueAnalysis())) {
            vector<pair<string, struct stat> >::const_iterator end
                = dirfiles.end();
            for (vector<pair<string, struct stat> >::const_iterator i
                    = dirfiles.begin(); i != end; ++i) {
                const string& filepath(i->first);
                struct stat s = i->second;
                AnalysisResult analysisresult(filepath, s.st_mtime,
                    indexWriter, *analyzer, parentpath);
                if (S_ISREG(s.st_mode)) {
                    FileInputStream file(filepath.c_str());
                    analysisresult.index(&file);
                } else {
                    analysisresult.index(0);
                }
                if (!config.indexMore()) return;
            }
            r = dirlister.nextDir(parentpath, dirfiles);
        }
    } catch(...) {
        fprintf(stderr, "Unknown error\n");
    }
}
void
DirAnalyzer::Private::update(StreamAnalyzer* analyzer) {
    IndexReader* reader = manager.indexReader();
    vector<pair<string, struct stat> > dirfiles;
    map<string, time_t> dbdirfiles;
    vector<string> toDelete;
    vector<pair<string, struct stat> > toIndex;
    try {
        string path;
        // loop over all files that exist in the index
        int r = dirlister.nextDir(path, dirfiles);
        while (r >= 0 && (caller == 0 || caller->continueAnalysis())) {
            if (r < 0) {
                continue;
            }
            // get the files that are in the current database
            reader->getChildren(path, dbdirfiles);

            // get all files in this directory
            vector<pair<string, struct stat> >::const_iterator end
                = dirfiles.end();
            map<string, time_t>::const_iterator dbend = dbdirfiles.end();
            for (vector<pair<string, struct stat> >::const_iterator i
                    = dirfiles.begin(); i != end; ++i) {
                const string& filepath(i->first);
                time_t mtime = i->second.st_mtime;

                // check if this file is new or not
                map<string, time_t>::iterator j = dbdirfiles.find(filepath);
                bool newfile = j == dbend;
                bool updatedfile = !newfile && j->second != mtime;

                if (newfile || (updatedfile && !S_ISDIR(i->second.st_mode))) {
                    // if the file has not yet been indexed or if the mtime has
                    // changed, index it
                    // if a directory has been updated, this will not change the index
                    // so the entry is not removed from the index, nor reindexed
                    toIndex.push_back(make_pair(filepath, i->second));
                } else {
                    // files left in dbdirfiles after this loop will be deleted from the
                    // index. because this file has not changed, it should not be
                    // removed from the index
                    dbdirfiles.erase(j);
                }
            }
            // all the files left in dbdirfiles, are not in the current
            // directory and should be deleted
            for (map<string, time_t>::const_iterator i = dbdirfiles.begin();
                    i != dbend; ++i) {
                toDelete.push_back(i->first);
            }
            if (toDelete.size() > 0) {
                manager.indexWriter()->deleteEntries(toDelete);
            }
            vector<pair<string, struct stat> >::const_iterator fend
                = toIndex.end();
            for (vector<pair<string, struct stat> >::const_iterator i
                    = toIndex.begin(); i != fend; ++i) {
                AnalysisResult analysisresult(i->first, i->second.st_mtime,
                    *manager.indexWriter(), *analyzer, path);
                if (S_ISREG(i->second.st_mode)) {
                    FileInputStream file(i->first.c_str());
                    analysisresult.index(&file);
                } else {
                    analysisresult.index(0);
                }
            }
            toDelete.clear();
            toIndex.clear();
            r = dirlister.nextDir(path, dirfiles);
        }
    } catch(...) {
        fprintf(stderr, "Unknown error\n");
    }
}
int
DirAnalyzer::analyzeDir(const string& dir, int nthreads, AnalysisCaller* c,
        const string& lastToSkip) {
    return p->analyzeDir(dir, nthreads, c, lastToSkip);
}
namespace {
string
removeTrailingSlash(const string& path) {
    string cleanpath(path);
    if (path.length() && path[path.length()-1] == '/') {
        cleanpath.resize(path.length()-1);
    }
    return cleanpath;
}
}
int
DirAnalyzer::Private::analyzeDir(const string& dir, int nthreads,
        AnalysisCaller* c, const string& lastToSkip) {
    caller = c;
    // check if the path exists and if it is a file or a directory
    struct stat s;
    const string path(removeTrailingSlash(dir));
    int retval;
    if (path.size() == 0) {
        // special case for analyzing the root directory '/' on unix
        retval = stat("/", &s);
    } else {
        retval = stat(path.c_str(), &s);
    }
    time_t mtime = (retval == -1) ?0 :s.st_mtime;
    bool isfile = (retval == -1) ?false :S_ISREG(s.st_mode);
    bool isdir = (retval == -1) ?false :S_ISDIR(s.st_mode);
    retval = analyzeFile(path, mtime, isfile);
    // if the path does not point to a directory, return
    if (!isdir) {
        manager.indexWriter()->commit();
        return retval;
    }
    dirlister.startListing(path);
    if (lastToSkip.length()) {
        dirlister.skipTillAfter(lastToSkip);
    }

    if (nthreads < 1) nthreads = 1;
    vector<StreamAnalyzer*> analyzers(nthreads);
    analyzers[0] = &analyzer;
    for (int i=1; i<nthreads; ++i) {
        analyzers[i] = new StreamAnalyzer(config);
        analyzers[i]->setIndexWriter(*manager.indexWriter());
    }
    vector<STRIGI_THREAD_TYPE> threads;
    threads.resize(nthreads-1);
    for (int i=1; i<nthreads; i++) {
        DA* da = new DA();
        da->diranalyzer = this;
        da->streamanalyzer = analyzers[i];
        STRIGI_THREAD_CREATE(&threads[i-1], analyzeInThread, da);
    }
    analyze(analyzers[0]);
    for (int i=1; i<nthreads; i++) {
        STRIGI_THREAD_JOIN(threads[i-1]);
        delete analyzers[i];
    }
    manager.indexWriter()->commit();
    return 0;
}
int
DirAnalyzer::updateDir(const string& dir, int nthreads, AnalysisCaller* caller){
    vector<string> dirs;
    dirs.push_back(dir);
    return p->updateDirs(dirs, nthreads, caller);
}
int
DirAnalyzer::Private::updateDirs(const vector<string>& dirs, int nthreads,
        AnalysisCaller* c) {
    IndexReader* reader = manager.indexReader();
    if (reader == 0) return -1;
    caller = c;

    // create the streamanalyzers
    if (nthreads < 1) nthreads = 1;
    vector<StreamAnalyzer*> analyzers(nthreads);
    analyzers[0] = &analyzer;
    for (int i=1; i<nthreads; ++i) {
        analyzers[i] = new StreamAnalyzer(config);
        analyzers[i]->setIndexWriter(*manager.indexWriter());
    }
    vector<STRIGI_THREAD_TYPE> threads;
    threads.resize(nthreads-1);

    // loop over all directories that should be updated
    for (vector<string>::const_iterator d =dirs.begin(); d != dirs.end(); ++d) {
        dirlister.startListing(removeTrailingSlash(*d));
        for (int i=1; i<nthreads; i++) {
            DA* da = new DA();
            da->diranalyzer = this;
            da->streamanalyzer = analyzers[i];
            STRIGI_THREAD_CREATE(&threads[i-1], updateInThread, da);
        }
        update(analyzers[0]);
        // wait until all threads have finished
        for (int i=1; i<nthreads; i++) {
            STRIGI_THREAD_JOIN(threads[i-1]);
        }
        dirlister.stopListing();
    }
    // clean up the analyzers
    for (int i=1; i<nthreads; i++) {
        delete analyzers[i];
    }

    // remove the files that were not encountered from the index
/*    vector<string> todelete(1);
    map<string,time_t>::iterator it = dbfiles.begin();
    while (it != dbfiles.end()) {
        todelete[0].assign(it->first);
        manager.indexWriter()->deleteEntries(todelete);
        ++it;
    }
    dbfiles.clear();*/
    manager.indexWriter()->commit();

    return 0;
}
int
DirAnalyzer::updateDirs(const vector<string>& dirs, int nthreads,
        AnalysisCaller* caller) {
    return p->updateDirs(dirs, nthreads, caller);
}
<|endoftext|>


<commit_before>/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/*
 * This file is part of the libetonyek project.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 */

#include <boost/math/constants/constants.hpp>

#include "KEYTransformation.h"
#include "KEYTypes.h"

#include "KEYTransformationTest.h"

namespace test
{

namespace m = boost::math::constants;

using libetonyek::KEYTransformation;

namespace
{

KEYTransformation wrap(const double width, const double height, const KEYTransformation &tr)
{
  using namespace libetonyek::transformations;
  return origin(width, height) * tr * center(width, height);
}

}

void KEYTransformationTest::setUp()
{
}

void KEYTransformationTest::tearDown()
{
}

void KEYTransformationTest::testApplication()
{
  using namespace libetonyek::transformations;

  // identity - point
  {
    double x = 20;
    double y = 40;
    KEYTransformation tr;
    tr(x, y);
    CPPUNIT_ASSERT_EQUAL(20.0, x);
    CPPUNIT_ASSERT_EQUAL(40.0, y);
  }

  // identity - distance
  {
    double x = 20;
    double y = 40;
    KEYTransformation tr;
    tr(x, y, true);
    CPPUNIT_ASSERT_EQUAL(20.0, x);
    CPPUNIT_ASSERT_EQUAL(40.0, y);
  }

  // translation - point
  {
    double x = 20;
    double y = 40;
    KEYTransformation tr = translate(10, 20);
    tr(x, y);
    CPPUNIT_ASSERT_EQUAL(30.0, x);
    CPPUNIT_ASSERT_EQUAL(60.0, y);
  }

  // translation - distance
  {
    double x = 20;
    double y = 40;
    KEYTransformation tr = translate(10, 20);
    tr(x, y, true);
    CPPUNIT_ASSERT_EQUAL(20.0, x);
    CPPUNIT_ASSERT_EQUAL(40.0, y);
  }

  // non-translating transformation - point
  {
    double x = 20;
    double y = 40;
    KEYTransformation tr = flip(true, false) * scale(0.25, 0.5);
    tr(x, y);
    CPPUNIT_ASSERT_EQUAL(-5.0, x);
    CPPUNIT_ASSERT_EQUAL(20.0, y);
  }

  // non-translating transformation - distance
  {
    double x = 20;
    double y = 40;
    KEYTransformation tr = flip(true, false) * scale(0.25, 0.5);
    tr(x, y, true);
    CPPUNIT_ASSERT_EQUAL(-5.0, x);
    CPPUNIT_ASSERT_EQUAL(20.0, y);
  }
}

void KEYTransformationTest::testConstruction()
{
  // identity
  CPPUNIT_ASSERT(KEYTransformation() == KEYTransformation(1, 0, 0, 1, 0, 0));

  using namespace libetonyek::transformations;

  // centering
  CPPUNIT_ASSERT(center(200, 100) == KEYTransformation(1, 0, 0, 1, 100, 50));
  CPPUNIT_ASSERT(origin(200, 100) == KEYTransformation(1, 0, 0, 1, -100, -50));

  // flipping
  CPPUNIT_ASSERT(flip(true, false) == KEYTransformation(-1, 0, 0, 1, 0, 0));
  CPPUNIT_ASSERT(flip(false, true) == KEYTransformation(1, 0, 0, -1, 0, 0));
  CPPUNIT_ASSERT(flip(true, true) == KEYTransformation(-1, 0, 0, -1, 0, 0));

  // rotating
  CPPUNIT_ASSERT(rotate(m::half_pi<double>()) == KEYTransformation(0, 1, -1, 0, 0, 0));

  // scaling
  CPPUNIT_ASSERT(scale(2, 1) == KEYTransformation(2, 0, 0, 1, 0, 0));
  CPPUNIT_ASSERT(scale(1, 2) == KEYTransformation(1, 0, 0, 2, 0, 0));
  CPPUNIT_ASSERT(scale(3, 2) == KEYTransformation(3, 0, 0, 2, 0, 0));

  // shearing
  // FIXME: find the problem and enable
  // CPPUNIT_ASSERT(shear(m::pi<double>() / 4, 0) == KEYTransformation(1, 2, 0, 1, 0, 0));
  // CPPUNIT_ASSERT(shear(0, m::pi<double>() / 4) == KEYTransformation(1, 0, 2, 1, 0, 0));
  // CPPUNIT_ASSERT(shear(m::pi<double>() / 4, m::pi<double>() / 4) == KEYTransformation(1, 2, 2, 1, 0, 0));

  // translating
  CPPUNIT_ASSERT(translate(100, 0) == KEYTransformation(1, 0, 0, 1, 100, 0));
  CPPUNIT_ASSERT(translate(0, 100) == KEYTransformation(1, 0, 0, 1, 0, 100));
  CPPUNIT_ASSERT(translate(300, 100) == KEYTransformation(1, 0, 0, 1, 300, 100));
}

void KEYTransformationTest::testConstructionIdentity()
{
  using namespace libetonyek::transformations;

  CPPUNIT_ASSERT(center(0, 0) == KEYTransformation());
  CPPUNIT_ASSERT(origin(0, 0) == KEYTransformation());
  CPPUNIT_ASSERT(flip(false, false) == KEYTransformation());
  CPPUNIT_ASSERT(rotate(0) == KEYTransformation());
  CPPUNIT_ASSERT(rotate(m::two_pi<double>()) == KEYTransformation());
  CPPUNIT_ASSERT(scale(1, 1) == KEYTransformation());
  CPPUNIT_ASSERT(shear(0, 0) == KEYTransformation());
  CPPUNIT_ASSERT(translate(0, 0) == KEYTransformation());
}

void KEYTransformationTest::testConstructionFromGeometry()
{
  using namespace libetonyek::transformations;

  using libetonyek::KEYGeometry;
  using libetonyek::KEYPosition;
  using libetonyek::KEYSize;

  {
    KEYGeometry g;
    g.naturalSize = KEYSize(100, 100);
    g.position = KEYPosition(0, 0);

    const KEYTransformation tr = makeTransformation(g);
    CPPUNIT_ASSERT(KEYTransformation() == tr);
  }

  {
    KEYGeometry g;
    g.naturalSize = KEYSize(100, 100);
    g.position = KEYPosition(200, 150);

    const KEYTransformation tr = makeTransformation(g);
    CPPUNIT_ASSERT(translate(200, 150) == tr);
  }

  {
    KEYGeometry g;
    g.naturalSize = KEYSize(100, 100);
    g.position = KEYPosition(0, 0);
    g.angle = m::half_pi<double>();

    const KEYTransformation tr = makeTransformation(g);
    CPPUNIT_ASSERT(wrap(100, 100, rotate(m::half_pi<double>())) == tr);
  }

  {
    KEYGeometry g;
    g.naturalSize = KEYSize(100, 100);
    g.position = KEYPosition(0, 0);
    g.horizontalFlip = true;

    const KEYTransformation tr = makeTransformation(g);
    CPPUNIT_ASSERT(wrap(100, 100, flip(true, false)) == tr);
  }

  {
    KEYGeometry g;
    g.naturalSize = KEYSize(100, 100);
    g.position = KEYPosition(0, 0);
    g.verticalFlip = true;

    const KEYTransformation tr = makeTransformation(g);
    CPPUNIT_ASSERT(wrap(100, 100, flip(false, true)) == tr);
  }

  {
    KEYGeometry g;
    g.naturalSize = KEYSize(100, 100);
    g.position = KEYPosition(200, 150);
    g.angle = m::half_pi<double>();

    const KEYTransformation tr = makeTransformation(g);
    CPPUNIT_ASSERT(wrap(100, 100, rotate(m::half_pi<double>()) * translate(200, 150)) == tr);
  }
}

void KEYTransformationTest::testIdentities()
{
  using namespace libetonyek::transformations;

  CPPUNIT_ASSERT(center(100, 50) == translate(50, 25));
  CPPUNIT_ASSERT(origin(100, 50) == translate(-50, -25));
  CPPUNIT_ASSERT((flip(true, false) * flip(false, true)) == flip(true, true));
  CPPUNIT_ASSERT((flip(false, true) * flip(true, false)) == flip(true, true));
  CPPUNIT_ASSERT((rotate(m::half_pi<double>()) * rotate(m::third_pi<double>())) == (rotate(m::third_pi<double>()) * rotate(m::half_pi<double>())));
  CPPUNIT_ASSERT(scale(-1, -1) == flip(true, true));
  CPPUNIT_ASSERT((translate(10, 20) * translate(80, 40)) == (translate(80, 40) * translate(10, 20)));
  CPPUNIT_ASSERT((translate(1, 2) * scale(2, 2)) == (scale(2, 2) * translate(2, 4)));
}

void KEYTransformationTest::testInverseOperations()
{
  using namespace libetonyek::transformations;

  CPPUNIT_ASSERT(center(10, 20) * origin(10, 20) == KEYTransformation());
  CPPUNIT_ASSERT(origin(10, 20) * center(10, 20) == KEYTransformation());

  CPPUNIT_ASSERT(flip(true, false) * flip(true, false) == KEYTransformation());
  CPPUNIT_ASSERT(flip(false, true) * flip(false, true) == KEYTransformation());
  CPPUNIT_ASSERT(flip(true, true) * flip(true, true) == KEYTransformation());

  CPPUNIT_ASSERT(rotate(m::pi<double>()) * rotate(-m::pi<double>()) == KEYTransformation());

  CPPUNIT_ASSERT(scale(2, 1) * scale(0.5, 1) == KEYTransformation());
  CPPUNIT_ASSERT(scale(1, 2) * scale(1, 0.5) == KEYTransformation());
  CPPUNIT_ASSERT(scale(3, 2) * scale(1.0 / 3, 0.5) == KEYTransformation());

  // CPPUNIT_ASSERT(shear() == KEYTransformation());

  CPPUNIT_ASSERT(translate(10, 20) * translate(-10, -20) == KEYTransformation());
}

void KEYTransformationTest::testMultiplication()
{
  using namespace libetonyek::transformations;

  CPPUNIT_ASSERT(KEYTransformation() * KEYTransformation() == KEYTransformation());

  CPPUNIT_ASSERT(KEYTransformation() * KEYTransformation(1, 2, 3, 4, 5, 6) == KEYTransformation(1, 2, 3, 4, 5, 6));
  CPPUNIT_ASSERT(KEYTransformation(1, 2, 3, 4, 5, 6) * KEYTransformation() == KEYTransformation(1, 2, 3, 4, 5, 6));
  CPPUNIT_ASSERT(KEYTransformation(1, 2, 3, 4, 5, 6) * KEYTransformation(6, 5, 4, 3, 2, 1) == KEYTransformation(14, 11, 34, 27, 56, 44));
}

CPPUNIT_TEST_SUITE_REGISTRATION(KEYTransformationTest);

}

/* vim:set shiftwidth=2 softtabstop=2 expandtab: */
<commit_msg>And these ones are not there either<commit_after>/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/*
 * This file is part of the libetonyek project.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 */

#include <boost/math/constants/constants.hpp>

#include "KEYTransformation.h"
#include "KEYTypes.h"

#include "KEYTransformationTest.h"

namespace test
{

namespace m = boost::math::constants;

using libetonyek::KEYTransformation;

const double etonyek_half_pi(1.57079632679489661923132169163975144209858469968755291048747229615390820314310449931401741267105853399107404326e+00);
const double etonyek_third_pi(1.04719755119659774615421446109316762806572313312503527365831486410260546876206966620934494178070568932738269550e+00);

namespace
{

KEYTransformation wrap(const double width, const double height, const KEYTransformation &tr)
{
  using namespace libetonyek::transformations;
  return origin(width, height) * tr * center(width, height);
}

}

void KEYTransformationTest::setUp()
{
}

void KEYTransformationTest::tearDown()
{
}

void KEYTransformationTest::testApplication()
{
  using namespace libetonyek::transformations;

  // identity - point
  {
    double x = 20;
    double y = 40;
    KEYTransformation tr;
    tr(x, y);
    CPPUNIT_ASSERT_EQUAL(20.0, x);
    CPPUNIT_ASSERT_EQUAL(40.0, y);
  }

  // identity - distance
  {
    double x = 20;
    double y = 40;
    KEYTransformation tr;
    tr(x, y, true);
    CPPUNIT_ASSERT_EQUAL(20.0, x);
    CPPUNIT_ASSERT_EQUAL(40.0, y);
  }

  // translation - point
  {
    double x = 20;
    double y = 40;
    KEYTransformation tr = translate(10, 20);
    tr(x, y);
    CPPUNIT_ASSERT_EQUAL(30.0, x);
    CPPUNIT_ASSERT_EQUAL(60.0, y);
  }

  // translation - distance
  {
    double x = 20;
    double y = 40;
    KEYTransformation tr = translate(10, 20);
    tr(x, y, true);
    CPPUNIT_ASSERT_EQUAL(20.0, x);
    CPPUNIT_ASSERT_EQUAL(40.0, y);
  }

  // non-translating transformation - point
  {
    double x = 20;
    double y = 40;
    KEYTransformation tr = flip(true, false) * scale(0.25, 0.5);
    tr(x, y);
    CPPUNIT_ASSERT_EQUAL(-5.0, x);
    CPPUNIT_ASSERT_EQUAL(20.0, y);
  }

  // non-translating transformation - distance
  {
    double x = 20;
    double y = 40;
    KEYTransformation tr = flip(true, false) * scale(0.25, 0.5);
    tr(x, y, true);
    CPPUNIT_ASSERT_EQUAL(-5.0, x);
    CPPUNIT_ASSERT_EQUAL(20.0, y);
  }
}

void KEYTransformationTest::testConstruction()
{
  // identity
  CPPUNIT_ASSERT(KEYTransformation() == KEYTransformation(1, 0, 0, 1, 0, 0));

  using namespace libetonyek::transformations;

  // centering
  CPPUNIT_ASSERT(center(200, 100) == KEYTransformation(1, 0, 0, 1, 100, 50));
  CPPUNIT_ASSERT(origin(200, 100) == KEYTransformation(1, 0, 0, 1, -100, -50));

  // flipping
  CPPUNIT_ASSERT(flip(true, false) == KEYTransformation(-1, 0, 0, 1, 0, 0));
  CPPUNIT_ASSERT(flip(false, true) == KEYTransformation(1, 0, 0, -1, 0, 0));
  CPPUNIT_ASSERT(flip(true, true) == KEYTransformation(-1, 0, 0, -1, 0, 0));

  // rotating
  CPPUNIT_ASSERT(rotate(etonyek_half_pi) == KEYTransformation(0, 1, -1, 0, 0, 0));

  // scaling
  CPPUNIT_ASSERT(scale(2, 1) == KEYTransformation(2, 0, 0, 1, 0, 0));
  CPPUNIT_ASSERT(scale(1, 2) == KEYTransformation(1, 0, 0, 2, 0, 0));
  CPPUNIT_ASSERT(scale(3, 2) == KEYTransformation(3, 0, 0, 2, 0, 0));

  // shearing
  // FIXME: find the problem and enable
  // CPPUNIT_ASSERT(shear(m::pi<double>() / 4, 0) == KEYTransformation(1, 2, 0, 1, 0, 0));
  // CPPUNIT_ASSERT(shear(0, m::pi<double>() / 4) == KEYTransformation(1, 0, 2, 1, 0, 0));
  // CPPUNIT_ASSERT(shear(m::pi<double>() / 4, m::pi<double>() / 4) == KEYTransformation(1, 2, 2, 1, 0, 0));

  // translating
  CPPUNIT_ASSERT(translate(100, 0) == KEYTransformation(1, 0, 0, 1, 100, 0));
  CPPUNIT_ASSERT(translate(0, 100) == KEYTransformation(1, 0, 0, 1, 0, 100));
  CPPUNIT_ASSERT(translate(300, 100) == KEYTransformation(1, 0, 0, 1, 300, 100));
}

void KEYTransformationTest::testConstructionIdentity()
{
  using namespace libetonyek::transformations;

  CPPUNIT_ASSERT(center(0, 0) == KEYTransformation());
  CPPUNIT_ASSERT(origin(0, 0) == KEYTransformation());
  CPPUNIT_ASSERT(flip(false, false) == KEYTransformation());
  CPPUNIT_ASSERT(rotate(0) == KEYTransformation());
  CPPUNIT_ASSERT(rotate(m::two_pi<double>()) == KEYTransformation());
  CPPUNIT_ASSERT(scale(1, 1) == KEYTransformation());
  CPPUNIT_ASSERT(shear(0, 0) == KEYTransformation());
  CPPUNIT_ASSERT(translate(0, 0) == KEYTransformation());
}

void KEYTransformationTest::testConstructionFromGeometry()
{
  using namespace libetonyek::transformations;

  using libetonyek::KEYGeometry;
  using libetonyek::KEYPosition;
  using libetonyek::KEYSize;

  {
    KEYGeometry g;
    g.naturalSize = KEYSize(100, 100);
    g.position = KEYPosition(0, 0);

    const KEYTransformation tr = makeTransformation(g);
    CPPUNIT_ASSERT(KEYTransformation() == tr);
  }

  {
    KEYGeometry g;
    g.naturalSize = KEYSize(100, 100);
    g.position = KEYPosition(200, 150);

    const KEYTransformation tr = makeTransformation(g);
    CPPUNIT_ASSERT(translate(200, 150) == tr);
  }

  {
    KEYGeometry g;
    g.naturalSize = KEYSize(100, 100);
    g.position = KEYPosition(0, 0);
    g.angle = etonyek_half_pi;

    const KEYTransformation tr = makeTransformation(g);
    CPPUNIT_ASSERT(wrap(100, 100, rotate(etonyek_half_pi)) == tr);
  }

  {
    KEYGeometry g;
    g.naturalSize = KEYSize(100, 100);
    g.position = KEYPosition(0, 0);
    g.horizontalFlip = true;

    const KEYTransformation tr = makeTransformation(g);
    CPPUNIT_ASSERT(wrap(100, 100, flip(true, false)) == tr);
  }

  {
    KEYGeometry g;
    g.naturalSize = KEYSize(100, 100);
    g.position = KEYPosition(0, 0);
    g.verticalFlip = true;

    const KEYTransformation tr = makeTransformation(g);
    CPPUNIT_ASSERT(wrap(100, 100, flip(false, true)) == tr);
  }

  {
    KEYGeometry g;
    g.naturalSize = KEYSize(100, 100);
    g.position = KEYPosition(200, 150);
    g.angle = etonyek_half_pi;

    const KEYTransformation tr = makeTransformation(g);
    CPPUNIT_ASSERT(wrap(100, 100, rotate(etonyek_half_pi) * translate(200, 150)) == tr);
  }
}

void KEYTransformationTest::testIdentities()
{
  using namespace libetonyek::transformations;

  CPPUNIT_ASSERT(center(100, 50) == translate(50, 25));
  CPPUNIT_ASSERT(origin(100, 50) == translate(-50, -25));
  CPPUNIT_ASSERT((flip(true, false) * flip(false, true)) == flip(true, true));
  CPPUNIT_ASSERT((flip(false, true) * flip(true, false)) == flip(true, true));
  CPPUNIT_ASSERT((rotate(etonyek_half_pi) * rotate(etonyek_third_pi)) == (rotate(etonyek_third_pi) * rotate(etonyek_half_pi)));
  CPPUNIT_ASSERT(scale(-1, -1) == flip(true, true));
  CPPUNIT_ASSERT((translate(10, 20) * translate(80, 40)) == (translate(80, 40) * translate(10, 20)));
  CPPUNIT_ASSERT((translate(1, 2) * scale(2, 2)) == (scale(2, 2) * translate(2, 4)));
}

void KEYTransformationTest::testInverseOperations()
{
  using namespace libetonyek::transformations;

  CPPUNIT_ASSERT(center(10, 20) * origin(10, 20) == KEYTransformation());
  CPPUNIT_ASSERT(origin(10, 20) * center(10, 20) == KEYTransformation());

  CPPUNIT_ASSERT(flip(true, false) * flip(true, false) == KEYTransformation());
  CPPUNIT_ASSERT(flip(false, true) * flip(false, true) == KEYTransformation());
  CPPUNIT_ASSERT(flip(true, true) * flip(true, true) == KEYTransformation());

  CPPUNIT_ASSERT(rotate(m::pi<double>()) * rotate(-m::pi<double>()) == KEYTransformation());

  CPPUNIT_ASSERT(scale(2, 1) * scale(0.5, 1) == KEYTransformation());
  CPPUNIT_ASSERT(scale(1, 2) * scale(1, 0.5) == KEYTransformation());
  CPPUNIT_ASSERT(scale(3, 2) * scale(1.0 / 3, 0.5) == KEYTransformation());

  // CPPUNIT_ASSERT(shear() == KEYTransformation());

  CPPUNIT_ASSERT(translate(10, 20) * translate(-10, -20) == KEYTransformation());
}

void KEYTransformationTest::testMultiplication()
{
  using namespace libetonyek::transformations;

  CPPUNIT_ASSERT(KEYTransformation() * KEYTransformation() == KEYTransformation());

  CPPUNIT_ASSERT(KEYTransformation() * KEYTransformation(1, 2, 3, 4, 5, 6) == KEYTransformation(1, 2, 3, 4, 5, 6));
  CPPUNIT_ASSERT(KEYTransformation(1, 2, 3, 4, 5, 6) * KEYTransformation() == KEYTransformation(1, 2, 3, 4, 5, 6));
  CPPUNIT_ASSERT(KEYTransformation(1, 2, 3, 4, 5, 6) * KEYTransformation(6, 5, 4, 3, 2, 1) == KEYTransformation(14, 11, 34, 27, 56, 44));
}

CPPUNIT_TEST_SUITE_REGISTRATION(KEYTransformationTest);

}

/* vim:set shiftwidth=2 softtabstop=2 expandtab: */
<|endoftext|>


<commit_before>#include <iostream>
#include <set>
#include <algorithm>
#include <functional>
#include <ode/ode.h>
#include <drawstuff/drawstuff.h>
#include "texturepath.h"

#ifdef dDOUBLE
#define dsDrawBox dsDrawBoxD
#define dsDrawCylinder dsDrawCylinderD
#endif

using namespace std;

dWorld *world;
dSpace *space;
dPlane *ground;
dBody *kbody;
dBox *kbox;
dJointGroup joints;
dCylinder *kpole;
dBody *matraca;
dBox *matraca_geom;
dHingeJoint *hinge;

struct Box {
    dBody body;
    dBox geom;
    Box() :
        body(*world),
        geom(*space, 0.2, 0.2, 0.2)
    {
        dMass mass;
        mass.setBox(1, 0.2, 0.2, 0.2);
        body.setMass(mass);
        geom.setData(this);
        geom.setBody(body);
    }
    void draw() const
    {
        dVector3 lengths;
        geom.getLengths(lengths);
        dsSetTexture(DS_WOOD);
        dsSetColor(0,1,0);
        dsDrawBox(geom.getPosition(), geom.getRotation(), lengths);
    }
};

set<Box*> boxes;
set<Box*> to_remove;

void dropBox()
{
    Box *box = new Box();

    dReal px = (rand() / float(RAND_MAX)) * 2 - 1;
    dReal py = (rand() / float(RAND_MAX)) * 2 - 1;
    dReal pz = 3;
    box->body.setPosition(px, py, pz);

    boxes.insert(box);
}

void queueRemoval(dGeomID g)
{
    Box *b = (Box*)dGeomGetData(g);
    to_remove.insert(b);
}

void removeQueued()
{
    while (!to_remove.empty()) {
        Box *b = *to_remove.begin();
        to_remove.erase(b);
        boxes.erase(b);
        delete b;
    }
}

void nearCallback(void *data, dGeomID g1, dGeomID g2)
{
    if (g1 == ground->id()) {
        queueRemoval(g2);
        return;
    }
    if (g2 == ground->id()) {
        queueRemoval(g1);
        return;
    }

    dBodyID b1 = dGeomGetBody(g1);
    dBodyID b2 = dGeomGetBody(g2);

    if (b1 and b2 and dAreConnectedExcluding(b1, b2, dJointTypeContact))
        return;

    const int MAX_CONTACTS = 10;
    dContact contact[MAX_CONTACTS];
    int n = dCollide(g1, g2, MAX_CONTACTS, &contact[0].geom, sizeof(dContact));
    for (int i=0; i<n; ++i) {
        contact[i].surface.mode = 0;
        contact[i].surface.mu = 1;
        dJointID j = dJointCreateContact (*world, joints.id(), contact+i);
        dJointAttach(j, b1, b2);
    }
}

void
simLoop(int pause)
{
    if (!pause) {
        const dReal timestep = 0.005;

        // this does a hard-coded circular motion animation
        static float t=0;
        t += timestep/4;
        if (t > 2*M_PI)
            t = 0;
        dReal px = cos(t);
        dReal py = sin(t);
        dReal vx = -sin(t)/4;
        dReal vy = cos(t)/4;
        kbody->setPosition(px, py, .5);
        kbody->setLinearVel(vx, vy, 0);
        // end of hard-coded animation

        space->collide(0, nearCallback);
        removeQueued();

        world->quickStep(timestep);
        joints.clear();
    }

    dVector3 lengths;

    // the moving platform
    kbox->getLengths(lengths);
    dsSetTexture(DS_WOOD);
    dsSetColor(.3, .3, 1);
    dsDrawBox(kbox->getPosition(), kbox->getRotation(), lengths);
    dReal length, radius;
    kpole->getParams(&radius, &length);
    dsSetTexture(DS_CHECKERED);
    dsSetColor(1, 1, 0);
    dsDrawCylinder(kpole->getPosition(), kpole->getRotation(), length, radius);

    // the matraca
    matraca_geom->getLengths(lengths);
    dsSetColor(1,0,0);
    dsSetTexture(DS_WOOD);
    dsDrawBox(matraca_geom->getPosition(), matraca_geom->getRotation(), lengths);

    // and the boxes
    for_each(boxes.begin(), boxes.end(), mem_fun(&Box::draw));
}

void command(int c)
{
    switch (c) {
        case ' ':
            dropBox();
            break;
    }
}

int main(int argc, char **argv)
{
    dInitODE();

    // setup pointers to drawstuff callback functions
    dsFunctions fn;
    fn.version = DS_VERSION;
    fn.start = 0;
    fn.step = &simLoop;
    fn.command = &command;
    fn.stop = 0;
    fn.path_to_textures = DRAWSTUFF_TEXTURE_PATH;

    cout << endl << "*** Press SPACE to drop boxes **" << endl;

    space = new dSimpleSpace();
    ground = new dPlane(*space, 0, 0, 1, 0);

    world = new dWorld;
    world->setGravity(0, 0, -.5);

    kbody = new dBody(*world);
    kbody->setKinematic();
    const dReal kx = 1, ky = 0, kz = .5;
    kbody->setPosition(kx, ky, kz);
    kbox = new dBox(*space, 3, 3, .5);
    kbox->setBody(*kbody);
    kpole = new dCylinder(*space, .125, 1.5);
    kpole->setBody(*kbody);
    dGeomSetOffsetPosition(kpole->id(), 0, 0, 0.8);

    matraca = new dBody(*world);
    matraca->setPosition(kx+0, ky+1, kz+1);
    matraca_geom = new dBox(*space, 0.5, 2, 0.75);
    matraca_geom->setBody(*matraca);
    dMass mass;
    mass.setBox(1, 0.5, 2, 0.75);
    matraca->setMass(mass);

    hinge = new dHingeJoint(*world);
    hinge->attach(*kbody, *matraca);
    hinge->setAnchor(kx, ky, kz+1);
    hinge->setAxis(0, 0, 1);

    dsSimulationLoop (argc, argv, 640, 480, &fn);

    dCloseODE();
}
<commit_msg>Fixed: Compilation error in Visual Studio fixed <commit_after>#include <iostream>
#include <set>
#include <algorithm>
#include <functional>
#include <ode/ode.h>
#include <drawstuff/drawstuff.h>
#include "texturepath.h"

#ifdef dDOUBLE
#define dsDrawBox dsDrawBoxD
#define dsDrawCylinder dsDrawCylinderD
#endif

using namespace std;

dWorld *world;
dSpace *space;
dPlane *ground;
dBody *kbody;
dBox *kbox;
dJointGroup joints;
dCylinder *kpole;
dBody *matraca;
dBox *matraca_geom;
dHingeJoint *hinge;

struct Box {
    dBody body;
    dBox geom;
    Box() :
        body(*world),
        geom(*space, 0.2, 0.2, 0.2)
    {
        dMass mass;
        mass.setBox(1, 0.2, 0.2, 0.2);
        body.setMass(mass);
        geom.setData(this);
        geom.setBody(body);
    }
    void draw() const
    {
        dVector3 lengths;
        geom.getLengths(lengths);
        dsSetTexture(DS_WOOD);
        dsSetColor(0,1,0);
        dsDrawBox(geom.getPosition(), geom.getRotation(), lengths);
    }
};

set<Box*> boxes;
set<Box*> to_remove;

void dropBox()
{
    Box *box = new Box();

    dReal px = (rand() / float(RAND_MAX)) * 2 - 1;
    dReal py = (rand() / float(RAND_MAX)) * 2 - 1;
    dReal pz = 3;
    box->body.setPosition(px, py, pz);

    boxes.insert(box);
}

void queueRemoval(dGeomID g)
{
    Box *b = (Box*)dGeomGetData(g);
    to_remove.insert(b);
}

void removeQueued()
{
    while (!to_remove.empty()) {
        Box *b = *to_remove.begin();
        to_remove.erase(b);
        boxes.erase(b);
        delete b;
    }
}

void nearCallback(void *data, dGeomID g1, dGeomID g2)
{
    if (g1 == ground->id()) {
        queueRemoval(g2);
        return;
    }
    if (g2 == ground->id()) {
        queueRemoval(g1);
        return;
    }

    dBodyID b1 = dGeomGetBody(g1);
    dBodyID b2 = dGeomGetBody(g2);

    if (b1 && b2 && dAreConnectedExcluding(b1, b2, dJointTypeContact))
        return;

    const int MAX_CONTACTS = 10;
    dContact contact[MAX_CONTACTS];
    int n = dCollide(g1, g2, MAX_CONTACTS, &contact[0].geom, sizeof(dContact));
    for (int i=0; i<n; ++i) {
        contact[i].surface.mode = 0;
        contact[i].surface.mu = 1;
        dJointID j = dJointCreateContact (*world, joints.id(), contact+i);
        dJointAttach(j, b1, b2);
    }
}

void
simLoop(int pause)
{
    if (!pause) {
        const dReal timestep = 0.005;

        // this does a hard-coded circular motion animation
        static float t=0;
        t += timestep/4;
        if (t > 2*M_PI)
            t = 0;
        dReal px = cos(t);
        dReal py = sin(t);
        dReal vx = -sin(t)/4;
        dReal vy = cos(t)/4;
        kbody->setPosition(px, py, .5);
        kbody->setLinearVel(vx, vy, 0);
        // end of hard-coded animation

        space->collide(0, nearCallback);
        removeQueued();

        world->quickStep(timestep);
        joints.clear();
    }

    dVector3 lengths;

    // the moving platform
    kbox->getLengths(lengths);
    dsSetTexture(DS_WOOD);
    dsSetColor(.3, .3, 1);
    dsDrawBox(kbox->getPosition(), kbox->getRotation(), lengths);
    dReal length, radius;
    kpole->getParams(&radius, &length);
    dsSetTexture(DS_CHECKERED);
    dsSetColor(1, 1, 0);
    dsDrawCylinder(kpole->getPosition(), kpole->getRotation(), length, radius);

    // the matraca
    matraca_geom->getLengths(lengths);
    dsSetColor(1,0,0);
    dsSetTexture(DS_WOOD);
    dsDrawBox(matraca_geom->getPosition(), matraca_geom->getRotation(), lengths);

    // and the boxes
    for_each(boxes.begin(), boxes.end(), mem_fun(&Box::draw));
}

void command(int c)
{
    switch (c) {
        case ' ':
            dropBox();
            break;
    }
}

int main(int argc, char **argv)
{
    dInitODE();

    // setup pointers to drawstuff callback functions
    dsFunctions fn;
    fn.version = DS_VERSION;
    fn.start = 0;
    fn.step = &simLoop;
    fn.command = &command;
    fn.stop = 0;
    fn.path_to_textures = DRAWSTUFF_TEXTURE_PATH;

    cout << endl << "*** Press SPACE to drop boxes **" << endl;

    space = new dSimpleSpace();
    ground = new dPlane(*space, 0, 0, 1, 0);

    world = new dWorld;
    world->setGravity(0, 0, -.5);

    kbody = new dBody(*world);
    kbody->setKinematic();
    const dReal kx = 1, ky = 0, kz = .5;
    kbody->setPosition(kx, ky, kz);
    kbox = new dBox(*space, 3, 3, .5);
    kbox->setBody(*kbody);
    kpole = new dCylinder(*space, .125, 1.5);
    kpole->setBody(*kbody);
    dGeomSetOffsetPosition(kpole->id(), 0, 0, 0.8);

    matraca = new dBody(*world);
    matraca->setPosition(kx+0, ky+1, kz+1);
    matraca_geom = new dBox(*space, 0.5, 2, 0.75);
    matraca_geom->setBody(*matraca);
    dMass mass;
    mass.setBox(1, 0.5, 2, 0.75);
    matraca->setMass(mass);

    hinge = new dHingeJoint(*world);
    hinge->attach(*kbody, *matraca);
    hinge->setAnchor(kx, ky, kz+1);
    hinge->setAxis(0, 0, 1);

    dsSimulationLoop (argc, argv, 640, 480, &fn);

    dCloseODE();
}
<|endoftext|>


<commit_before>//
// Copyright (C) 2011 Mateusz Loskot <mateusz@loskot.net>
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE_1_0.txt or copy at
// http://www.boost.org/LICENSE_1_0.txt)
//
// Blog article: http://mateusz.loskot.net/?p=2819

// http://python3porting.com/cextensions.html

#ifndef PDAL_PLANG_REDIRECTOR_H
#define PDAL_PLANG_REDIRECTOR_H

#include <pdal/pdal_internal.hpp>
#ifdef PDAL_HAVE_PYTHON

#include <pdal/pdal_internal.hpp>
#include <pdal/PointBuffer.hpp>

#include <boost/cstdint.hpp>
#include <boost/variant.hpp>

#include <vector>
#include <iostream>

#include <Python.h>

namespace pdal
{
namespace plang
{

PyMODINIT_FUNC redirector_init(void);

class Redirector
{
public:
    typedef std::function<void(std::string)> stdout_write_type;

    static void init();
    static void set_stdout(stdout_write_type write);
    static void reset_stdout();

private:
    // Internal state
    static PyObject* g_stdout;
    static PyObject* g_stdout_saved;
};

} // namespaces
}

#endif

#endif
<commit_msg>gcc fix?<commit_after>//
// Copyright (C) 2011 Mateusz Loskot <mateusz@loskot.net>
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE_1_0.txt or copy at
// http://www.boost.org/LICENSE_1_0.txt)
//
// Blog article: http://mateusz.loskot.net/?p=2819

// http://python3porting.com/cextensions.html

#ifndef PDAL_PLANG_REDIRECTOR_H
#define PDAL_PLANG_REDIRECTOR_H

#include <pdal/pdal_internal.hpp>
#ifdef PDAL_HAVE_PYTHON

#include <pdal/pdal_internal.hpp>
#include <pdal/PointBuffer.hpp>

#include <boost/cstdint.hpp>
#include <boost/variant.hpp>
#include <functional>
#include <vector>
#include <iostream>

#include <Python.h>

namespace pdal
{
namespace plang
{

PyMODINIT_FUNC redirector_init(void);

class Redirector
{
public:
    typedef std::function<void(std::string)> stdout_write_type;

    static void init();
    static void set_stdout(stdout_write_type write);
    static void reset_stdout();

private:
    // Internal state
    static PyObject* g_stdout;
    static PyObject* g_stdout_saved;
};

} // namespaces
}

#endif

#endif
<|endoftext|>


<commit_before>#include "make.h"

#include <string>

#include "config/settingsNode.h"
#include "core/patterns.h"
#include "core/patternsHandler.h"
#include "core/task.h"

#include "configValue.h"
#include "pluginUtils.h"

using std::string;
using execHelper::core::Task;
using execHelper::core::Command;
using execHelper::core::Options;
using execHelper::core::TaskCollection;
using execHelper::core::PatternKeys;
using execHelper::config::SettingsNode;

namespace execHelper { namespace plugins {
    bool Make::apply(const Command& command, Task task, const Options& options) const noexcept {
        static const string MAKE_COMMAND("make");

        const SettingsNode& rootSettings = options.getSettings({"make"});
        task.append(MAKE_COMMAND);

        if(getMultiThreaded(command, rootSettings, options)) {
            task.append(TaskCollection({"--jobs", "8"}));
        }
        string buildDir = ConfigValue<string>::get(getBuildDirKey(), "", command, rootSettings);
        if(!buildDir.empty()) {
            task.append("--directory=" + buildDir);
        }
        if(options.getVerbosity()) {
            task.append("--debug");
        }
        task.append(ConfigValue<TaskCollection>::get(getCommandLineKey(), {}, command, rootSettings));
        task.appendToEnvironment(getEnvironment(command, rootSettings));

        for(const auto& combination : makePatternPermutator(command, rootSettings, options)) {
            Task makeTask = replacePatternCombinations(task, combination);
            if(! registerTask(makeTask, options)) {
                return false;
            }
        }
        return true;
    }
} // namespace plugins
} // namespace execHelper
<commit_msg>Fix: CI: Lowered building with multiple threads to fix issues when compiling on the CI system.<commit_after>#include "make.h"

#include <string>

#include "config/settingsNode.h"
#include "core/patterns.h"
#include "core/patternsHandler.h"
#include "core/task.h"

#include "configValue.h"
#include "pluginUtils.h"

using std::string;
using execHelper::core::Task;
using execHelper::core::Command;
using execHelper::core::Options;
using execHelper::core::TaskCollection;
using execHelper::core::PatternKeys;
using execHelper::config::SettingsNode;

namespace execHelper { namespace plugins {
    bool Make::apply(const Command& command, Task task, const Options& options) const noexcept {
        static const string MAKE_COMMAND("make");

        const SettingsNode& rootSettings = options.getSettings({"make"});
        task.append(MAKE_COMMAND);

        if(getMultiThreaded(command, rootSettings, options)) {
            task.append(TaskCollection({"--jobs", "4"}));
        }
        string buildDir = ConfigValue<string>::get(getBuildDirKey(), "", command, rootSettings);
        if(!buildDir.empty()) {
            task.append("--directory=" + buildDir);
        }
        if(options.getVerbosity()) {
            task.append("--debug");
        }
        task.append(ConfigValue<TaskCollection>::get(getCommandLineKey(), {}, command, rootSettings));
        task.appendToEnvironment(getEnvironment(command, rootSettings));

        for(const auto& combination : makePatternPermutator(command, rootSettings, options)) {
            Task makeTask = replacePatternCombinations(task, combination);
            if(! registerTask(makeTask, options)) {
                return false;
            }
        }
        return true;
    }
} // namespace plugins
} // namespace execHelper
<|endoftext|>


<commit_before>/*
 * Copyright 2010 Google Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

// Author: jmarantz@google.com (Joshua Marantz)
//
// Implementation of ResourceCombiner, a helper for filters that combine
// multiple resources. Also contains CombinerCallback, which is used to collect
// input resources when doing a ResourceCombiner::Fetch.

#include "net/instaweb/rewriter/public/resource_combiner.h"

#include <cstddef>

#include "base/logging.h"
#include "net/instaweb/rewriter/cached_result.pb.h"
#include "net/instaweb/rewriter/public/output_resource.h"
#include "net/instaweb/rewriter/public/output_resource_kind.h"
#include "net/instaweb/rewriter/public/resource.h"
#include "net/instaweb/rewriter/public/server_context.h"
#include "net/instaweb/rewriter/public/resource_namer.h"
#include "net/instaweb/rewriter/public/rewrite_driver.h"
#include "net/instaweb/rewriter/public/rewrite_filter.h"
#include "net/instaweb/rewriter/public/rewrite_options.h"
#include "net/instaweb/rewriter/public/url_partnership.h"
#include "net/instaweb/util/public/hasher.h"
#include "net/instaweb/util/public/message_handler.h"
#include "net/instaweb/util/public/ref_counted_ptr.h"
#include "net/instaweb/util/public/string.h"
#include "net/instaweb/util/public/string_util.h"
#include "net/instaweb/util/public/string_writer.h"
#include "net/instaweb/util/public/url_escaper.h"
#include "net/instaweb/util/public/url_multipart_encoder.h"
#include "net/instaweb/util/public/writer.h"
#include "pagespeed/kernel/http/response_headers.h"

namespace net_instaweb {

ResourceCombiner::ResourceCombiner(RewriteDriver* driver,
                                   const StringPiece& extension,
                                   RewriteFilter* filter)
    : server_context_(driver->server_context()),
      rewrite_driver_(driver),
      partnership_(driver),
      prev_num_components_(0),
      accumulated_leaf_size_(0),
      // TODO(jmarantz): The URL overhead computation is arguably fragile.
      // Another approach is to put a CHECK that the final URL with the
      // resource naming does not exceed the limit.
      //
      // Another option too is to just instantiate a ResourceNamer and a
      // hasher put in the correct ID and EXT and leave the name blank and
      // take size of that.
      url_overhead_(strlen(filter->id()) + ResourceNamer::kOverhead +
                    extension.size()),
      filter_(filter) {
  // This CHECK is here because RewriteDriver is constructed with its
  // server_context_ == NULL.
  // TODO(sligocki): Construct RewriteDriver with a ServerContext, to avoid
  // worrying about it not getting initialized.
  CHECK(server_context_ != NULL);
}

ResourceCombiner::~ResourceCombiner() {
  Clear();
}

TimedBool ResourceCombiner::AddResourceNoFetch(const ResourcePtr& resource,
                                               MessageHandler* handler) {
  TimedBool ret = {0, false};

  // Assert the sanity of three parallel vectors.
  CHECK_EQ(num_urls(), static_cast<int>(resources_.size()));
  CHECK_EQ(num_urls(), static_cast<int>(multipart_encoder_urls_.size()));
  if (num_urls() == 0) {
    // Make sure to initialize the base URL.
    Reset();
  }

  // From here on out, the answer will not change until the resource itself
  // does.
  ret.expiration_ms = resource->CacheExpirationTimeMs();

  // Make sure the specific filter is OK with the data --- it may be
  // unable to combine it safely
  GoogleString failure_reason;
  if (!ResourceCombinable(resource.get(), &failure_reason, handler)) {
    handler->Message(
        kInfo, "Cannot combine %s: resource not combinable, reason: %s",
        resource->url().c_str(), failure_reason.c_str());
    return ret;
  }

  // Now manage the URL and policy.
  bool added = partnership_.AddUrl(resource->url(), handler);

  if (added) {
    int index = num_urls() - 1;

    if (partnership_.NumCommonComponents() != prev_num_components_) {
      UpdateResolvedBase();
    }
    const GoogleString relative_path = partnership_.RelativePath(index);
    multipart_encoder_urls_.push_back(relative_path);

    if (accumulated_leaf_size_ == 0) {
      ComputeLeafSize();
    } else {
      AccumulateLeafSize(relative_path);
    }

    AccumulateCombinedSize(resource);

    resources_.push_back(resource);
    if (ContentSizeTooBig() || UrlTooBig()) {
      // TODO(ksimbili) : Propagate the correct reason-string to the caller.
      handler->Message(kInfo, "Cannot combine: contents/url size too big");
      RemoveLastResource();
      added = false;
    }
  } else {
    handler->Message(kInfo, "Cannot combine: partnership forbids");
  }
  ret.value = added;
  return ret;
}

void ResourceCombiner::RemoveLastResource() {
  partnership_.RemoveLast();
  resources_.pop_back();
  multipart_encoder_urls_.pop_back();
  if (partnership_.NumCommonComponents() != prev_num_components_) {
    UpdateResolvedBase();
  }
}

GoogleString ResourceCombiner::UrlSafeId() const {
  GoogleString segment;
  UrlMultipartEncoder encoder;
  encoder.Encode(multipart_encoder_urls_, NULL, &segment);
  return segment;
}

void ResourceCombiner::ComputeLeafSize() {
  GoogleString segment = UrlSafeId();
  accumulated_leaf_size_ = segment.size() + url_overhead_
      + server_context_->hasher()->HashSizeInChars();
}

void ResourceCombiner::AccumulateLeafSize(const StringPiece& url) {
  GoogleString segment;
  UrlEscaper::EncodeToUrlSegment(url, &segment);
  const int kMultipartOverhead = 1;  // for the '+'
  accumulated_leaf_size_ += segment.size() + kMultipartOverhead;
}

bool ResourceCombiner::UrlTooBig() {
  // Note: We include kUrlSlack in our computations so that other filters,
  // which might add to URL length, can run after ours
  int expanded_size = accumulated_leaf_size_ + ResourceCombiner::kUrlSlack;

  if (expanded_size > rewrite_driver_->options()->max_url_segment_size()) {
    return true;
  }

  if ((expanded_size + static_cast<int>(resolved_base_.size())) >
      rewrite_driver_->options()->max_url_size()) {
    return true;
  }
  return false;
}

bool ResourceCombiner::ResourceCombinable(
    Resource* /*resource*/,
    GoogleString* /*failure_reason*/,
    MessageHandler* /*handler*/) {
  return true;
}

void ResourceCombiner::UpdateResolvedBase() {
  // If the addition of this URL changes the base path,
  // then we will have to recompute the multi-part encoding.
  // This is n^2 in the pathological case and if this code
  // gets used for image spriting then this
  // algorithm should be revisited.  For CSS and JS we expect N to
  // be relatively small.
  prev_num_components_ = partnership_.NumCommonComponents();
  resolved_base_ = ResolvedBase();
  multipart_encoder_urls_.clear();
  for (size_t i = 0; i < resources_.size(); ++i) {
    multipart_encoder_urls_.push_back(partnership_.RelativePath(i));
  }

  accumulated_leaf_size_ = 0;
}

OutputResourcePtr ResourceCombiner::Combine(MessageHandler* handler) {
  OutputResourcePtr combination;
  if (resources_.size() <= 1) {
    // No point in combining.
    return combination;
  }
  // First, compute the name of the new resource based on the names of
  // the old resources.
  GoogleString url_safe_id = UrlSafeId();
  // Start building up the combination.  At this point we are still
  // not committed to the combination, because the 'write' can fail.
  // TODO(jmaessen, jmarantz): encode based on partnership
  GoogleString resolved_base = ResolvedBase();
  combination.reset(rewrite_driver_->CreateOutputResourceWithMappedPath(
      resolved_base, resolved_base, filter_->id(), url_safe_id,
      kRewrittenResource));
  if (combination.get() != NULL) {
    if (combination->cached_result() != NULL &&
        combination->cached_result()->optimizable()) {
      // If the combination has a Url set on it we have cached information
      // on what the output would be, so we'll just use that.
      return combination;
    }
    if (WriteCombination(resources_, combination, handler)
        && combination->IsWritten()) {
      // Otherwise, we have to compute it.
      return combination;
    }
    // No dice.
    combination.clear();
  }
  return combination;
}

bool ResourceCombiner::WriteCombination(
    const ResourceVector& combine_resources,
    const OutputResourcePtr& combination,
    MessageHandler* handler) {
  bool written = true;
  // TODO(sligocki): Write directly to a temp file rather than doing the extra
  // string copy.
  GoogleString combined_contents;
  StringWriter writer(&combined_contents);
  for (int i = 0, n = combine_resources.size(); written && (i < n); ++i) {
    ResourcePtr input(combine_resources[i]);
    written = WritePiece(i, input.get(), combination.get(), &writer, handler);
  }
  if (written) {
    // Intersect the response headers from each input.
    ResponseHeaders* output_headers = combination->response_headers();
    DCHECK_EQ(0, output_headers->NumAttributes());

    // We don't copy over all the resources from [0] because we don't
    // want the input cache-control.  The output cache-control is set via
    // RewriteDriver::Write when it calls
    // RewriteDriver::SetDefaultLongCacheHeaders.
    server_context_->MergeNonCachingResponseHeaders(
        *combine_resources[0]->response_headers(), output_headers);
    for (int i = 1, n = combine_resources.size(); i < n; ++i) {
      output_headers->RemoveIfNotIn(*combine_resources[i]->response_headers());
    }

    // TODO(morlovich): Fix combiners to deal with charsets.
    written =
        rewrite_driver_->Write(
            combine_resources, combined_contents, CombinationContentType(),
            StringPiece() /* not computing charset for now */,
            combination.get());
  }
  return written;
}

bool ResourceCombiner::WritePiece(int index,
                                  const Resource* input,
                                  OutputResource* /*combination*/,
                                  Writer* writer,
                                  MessageHandler* handler) {
  return writer->Write(input->contents(), handler);
}

void ResourceCombiner::Clear() {
  resources_.clear();
  multipart_encoder_urls_.clear();
}

void ResourceCombiner::Reset() {
  Clear();
  partnership_.Reset(rewrite_driver_->base_url());
  prev_num_components_ = 0;
  accumulated_leaf_size_ = 0;
  resolved_base_.clear();
}

}  // namespace net_instaweb
<commit_msg>Trivial fix to make it clearer why combining is failing, since the two failure modes are pretty different.<commit_after>/*
 * Copyright 2010 Google Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

// Author: jmarantz@google.com (Joshua Marantz)
//
// Implementation of ResourceCombiner, a helper for filters that combine
// multiple resources. Also contains CombinerCallback, which is used to collect
// input resources when doing a ResourceCombiner::Fetch.

#include "net/instaweb/rewriter/public/resource_combiner.h"

#include <cstddef>

#include "base/logging.h"
#include "net/instaweb/rewriter/cached_result.pb.h"
#include "net/instaweb/rewriter/public/output_resource.h"
#include "net/instaweb/rewriter/public/output_resource_kind.h"
#include "net/instaweb/rewriter/public/resource.h"
#include "net/instaweb/rewriter/public/server_context.h"
#include "net/instaweb/rewriter/public/resource_namer.h"
#include "net/instaweb/rewriter/public/rewrite_driver.h"
#include "net/instaweb/rewriter/public/rewrite_filter.h"
#include "net/instaweb/rewriter/public/rewrite_options.h"
#include "net/instaweb/rewriter/public/url_partnership.h"
#include "net/instaweb/util/public/hasher.h"
#include "net/instaweb/util/public/message_handler.h"
#include "net/instaweb/util/public/ref_counted_ptr.h"
#include "net/instaweb/util/public/string.h"
#include "net/instaweb/util/public/string_util.h"
#include "net/instaweb/util/public/string_writer.h"
#include "net/instaweb/util/public/url_escaper.h"
#include "net/instaweb/util/public/url_multipart_encoder.h"
#include "net/instaweb/util/public/writer.h"
#include "pagespeed/kernel/http/response_headers.h"

namespace net_instaweb {

ResourceCombiner::ResourceCombiner(RewriteDriver* driver,
                                   const StringPiece& extension,
                                   RewriteFilter* filter)
    : server_context_(driver->server_context()),
      rewrite_driver_(driver),
      partnership_(driver),
      prev_num_components_(0),
      accumulated_leaf_size_(0),
      // TODO(jmarantz): The URL overhead computation is arguably fragile.
      // Another approach is to put a CHECK that the final URL with the
      // resource naming does not exceed the limit.
      //
      // Another option too is to just instantiate a ResourceNamer and a
      // hasher put in the correct ID and EXT and leave the name blank and
      // take size of that.
      url_overhead_(strlen(filter->id()) + ResourceNamer::kOverhead +
                    extension.size()),
      filter_(filter) {
  // This CHECK is here because RewriteDriver is constructed with its
  // server_context_ == NULL.
  // TODO(sligocki): Construct RewriteDriver with a ServerContext, to avoid
  // worrying about it not getting initialized.
  CHECK(server_context_ != NULL);
}

ResourceCombiner::~ResourceCombiner() {
  Clear();
}

TimedBool ResourceCombiner::AddResourceNoFetch(const ResourcePtr& resource,
                                               MessageHandler* handler) {
  TimedBool ret = {0, false};

  // Assert the sanity of three parallel vectors.
  CHECK_EQ(num_urls(), static_cast<int>(resources_.size()));
  CHECK_EQ(num_urls(), static_cast<int>(multipart_encoder_urls_.size()));
  if (num_urls() == 0) {
    // Make sure to initialize the base URL.
    Reset();
  }

  // From here on out, the answer will not change until the resource itself
  // does.
  ret.expiration_ms = resource->CacheExpirationTimeMs();

  // Make sure the specific filter is OK with the data --- it may be
  // unable to combine it safely
  GoogleString failure_reason;
  if (!ResourceCombinable(resource.get(), &failure_reason, handler)) {
    handler->Message(
        kInfo, "Cannot combine %s: resource not combinable, reason: %s",
        resource->url().c_str(), failure_reason.c_str());
    return ret;
  }

  // Now manage the URL and policy.
  bool added = partnership_.AddUrl(resource->url(), handler);

  if (added) {
    int index = num_urls() - 1;

    if (partnership_.NumCommonComponents() != prev_num_components_) {
      UpdateResolvedBase();
    }
    const GoogleString relative_path = partnership_.RelativePath(index);
    multipart_encoder_urls_.push_back(relative_path);

    if (accumulated_leaf_size_ == 0) {
      ComputeLeafSize();
    } else {
      AccumulateLeafSize(relative_path);
    }

    AccumulateCombinedSize(resource);

    resources_.push_back(resource);
    const char* failure_reason = NULL;
    if (ContentSizeTooBig()) {
      failure_reason = "combined contents too big.";
    } else if (UrlTooBig()) {
      failure_reason = "combined url too long.";
    }
    if (failure_reason != NULL) {
      handler->Message(
          kInfo, "Cannot combine %s: %s",
          resource->url().c_str(), failure_reason);
      RemoveLastResource();
      added = false;
    }
  } else {
    handler->Message(kInfo, "Cannot combine: partnership forbids");
  }
  ret.value = added;
  return ret;
}

void ResourceCombiner::RemoveLastResource() {
  partnership_.RemoveLast();
  resources_.pop_back();
  multipart_encoder_urls_.pop_back();
  if (partnership_.NumCommonComponents() != prev_num_components_) {
    UpdateResolvedBase();
  }
}

GoogleString ResourceCombiner::UrlSafeId() const {
  GoogleString segment;
  UrlMultipartEncoder encoder;
  encoder.Encode(multipart_encoder_urls_, NULL, &segment);
  return segment;
}

void ResourceCombiner::ComputeLeafSize() {
  GoogleString segment = UrlSafeId();
  accumulated_leaf_size_ = segment.size() + url_overhead_
      + server_context_->hasher()->HashSizeInChars();
}

void ResourceCombiner::AccumulateLeafSize(const StringPiece& url) {
  GoogleString segment;
  UrlEscaper::EncodeToUrlSegment(url, &segment);
  const int kMultipartOverhead = 1;  // for the '+'
  accumulated_leaf_size_ += segment.size() + kMultipartOverhead;
}

bool ResourceCombiner::UrlTooBig() {
  // Note: We include kUrlSlack in our computations so that other filters,
  // which might add to URL length, can run after ours
  int expanded_size = accumulated_leaf_size_ + ResourceCombiner::kUrlSlack;

  if (expanded_size > rewrite_driver_->options()->max_url_segment_size()) {
    return true;
  }

  if ((expanded_size + static_cast<int>(resolved_base_.size())) >
      rewrite_driver_->options()->max_url_size()) {
    return true;
  }
  return false;
}

bool ResourceCombiner::ResourceCombinable(
    Resource* /*resource*/,
    GoogleString* /*failure_reason*/,
    MessageHandler* /*handler*/) {
  return true;
}

void ResourceCombiner::UpdateResolvedBase() {
  // If the addition of this URL changes the base path,
  // then we will have to recompute the multi-part encoding.
  // This is n^2 in the pathological case and if this code
  // gets used for image spriting then this
  // algorithm should be revisited.  For CSS and JS we expect N to
  // be relatively small.
  prev_num_components_ = partnership_.NumCommonComponents();
  resolved_base_ = ResolvedBase();
  multipart_encoder_urls_.clear();
  for (size_t i = 0; i < resources_.size(); ++i) {
    multipart_encoder_urls_.push_back(partnership_.RelativePath(i));
  }

  accumulated_leaf_size_ = 0;
}

OutputResourcePtr ResourceCombiner::Combine(MessageHandler* handler) {
  OutputResourcePtr combination;
  if (resources_.size() <= 1) {
    // No point in combining.
    return combination;
  }
  // First, compute the name of the new resource based on the names of
  // the old resources.
  GoogleString url_safe_id = UrlSafeId();
  // Start building up the combination.  At this point we are still
  // not committed to the combination, because the 'write' can fail.
  // TODO(jmaessen, jmarantz): encode based on partnership
  GoogleString resolved_base = ResolvedBase();
  combination.reset(rewrite_driver_->CreateOutputResourceWithMappedPath(
      resolved_base, resolved_base, filter_->id(), url_safe_id,
      kRewrittenResource));
  if (combination.get() != NULL) {
    if (combination->cached_result() != NULL &&
        combination->cached_result()->optimizable()) {
      // If the combination has a Url set on it we have cached information
      // on what the output would be, so we'll just use that.
      return combination;
    }
    if (WriteCombination(resources_, combination, handler)
        && combination->IsWritten()) {
      // Otherwise, we have to compute it.
      return combination;
    }
    // No dice.
    combination.clear();
  }
  return combination;
}

bool ResourceCombiner::WriteCombination(
    const ResourceVector& combine_resources,
    const OutputResourcePtr& combination,
    MessageHandler* handler) {
  bool written = true;
  // TODO(sligocki): Write directly to a temp file rather than doing the extra
  // string copy.
  GoogleString combined_contents;
  StringWriter writer(&combined_contents);
  for (int i = 0, n = combine_resources.size(); written && (i < n); ++i) {
    ResourcePtr input(combine_resources[i]);
    written = WritePiece(i, input.get(), combination.get(), &writer, handler);
  }
  if (written) {
    // Intersect the response headers from each input.
    ResponseHeaders* output_headers = combination->response_headers();
    DCHECK_EQ(0, output_headers->NumAttributes());

    // We don't copy over all the resources from [0] because we don't
    // want the input cache-control.  The output cache-control is set via
    // RewriteDriver::Write when it calls
    // RewriteDriver::SetDefaultLongCacheHeaders.
    server_context_->MergeNonCachingResponseHeaders(
        *combine_resources[0]->response_headers(), output_headers);
    for (int i = 1, n = combine_resources.size(); i < n; ++i) {
      output_headers->RemoveIfNotIn(*combine_resources[i]->response_headers());
    }

    // TODO(morlovich): Fix combiners to deal with charsets.
    written =
        rewrite_driver_->Write(
            combine_resources, combined_contents, CombinationContentType(),
            StringPiece() /* not computing charset for now */,
            combination.get());
  }
  return written;
}

bool ResourceCombiner::WritePiece(int index,
                                  const Resource* input,
                                  OutputResource* /*combination*/,
                                  Writer* writer,
                                  MessageHandler* handler) {
  return writer->Write(input->contents(), handler);
}

void ResourceCombiner::Clear() {
  resources_.clear();
  multipart_encoder_urls_.clear();
}

void ResourceCombiner::Reset() {
  Clear();
  partnership_.Reset(rewrite_driver_->base_url());
  prev_num_components_ = 0;
  accumulated_leaf_size_ = 0;
  resolved_base_.clear();
}

}  // namespace net_instaweb
<|endoftext|>


<commit_before>/*
 * Copyright (C) 2007 Tommi Maekitalo
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * As a special exception, you may use this file as part of a free
 * software library without restriction. Specifically, if other files
 * instantiate templates or use macros or inline functions from this
 * file, or you compile this file and link it with other files to
 * produce an executable, this file does not by itself cause the
 * resulting executable to be covered by the GNU General Public
 * License. This exception does not however invalidate any other
 * reasons why the executable file might be covered by the GNU Library
 * General Public License.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 */

#include <cxxtools/posix/pipestream.h>
#include <cxxtools/systemerror.h>
#include <algorithm>
#include <unistd.h>
#include <cxxtools/log.h>
#include <cstring>
#include <errno.h>
#include <unistd.h>
#include <fcntl.h>

log_define("cxxtools.pipestream")

namespace cxxtools
{
namespace posix
{
  Pipestreambuf::Pipestreambuf(unsigned bufsize_)
    : bufsize(bufsize_),
      ibuffer(0),
      obuffer(0)
  { }

  Pipestreambuf::~Pipestreambuf()
  {
    log_debug("Pipestreambuf::~Pipestreambuf()");

    try
    {
      closeReadFd();
    }
    catch (const std::exception& e)
    {
      log_debug("ignore exception in closing read pipe: " << e.what());
    }

    try
    {
      closeWriteFd();
    }
    catch (const std::exception& e)
    {
      log_debug("ignore exception in closing write pipe: " << e.what());
    }

    delete [] ibuffer;
    delete [] obuffer;
  }

  std::streambuf::int_type Pipestreambuf::overflow(std::streambuf::int_type ch)
  {
    log_debug("overflow(" << ch << ')');

    if (pptr() != pbase())
    {
      log_debug("write " << (pptr() - pbase()) << " bytes to fd " << getWriteFd());
      ssize_t ret = ::write(getWriteFd(), pbase(), pptr() - pbase());

      if(ret < 0)
        throw SystemError(errno, "write");

      if (ret == 0)
        return traits_type::eof();
      else
      {
        log_debug(ret << " bytes written to fd " << getWriteFd());
        if (static_cast<unsigned>(ret) < bufsize)
          std::memmove(obuffer, obuffer + ret, bufsize - ret);
        setp(obuffer, obuffer + bufsize);
        pbump(bufsize - ret);
      }
    }
    else
    {
      log_debug("initialize outputbuffer");
      if (obuffer == 0)
      {
        log_debug("allocate " << bufsize << " bytes output buffer");
        obuffer = new char[bufsize];
      }

      setp(obuffer, obuffer + bufsize);
    }

    if (ch != traits_type::eof())
    {
      *pptr() = traits_type::to_char_type(ch);
      pbump(1);
    }

    return 0;
  }

  std::streambuf::int_type Pipestreambuf::underflow()
  {
    log_debug("underflow()");

    if (ibuffer == 0)
    {
      log_debug("allocate " << bufsize << " bytes input buffer");
      ibuffer = new char[bufsize];
    }

    log_debug("read from fd " << getReadFd());
    int ret = ::read(getReadFd(), ibuffer, bufsize);
    log_debug("read returned " << ret);

    if(ret < 0)
      throw SystemError(errno, "read");

    if (ret == 0)
      return traits_type::eof();

    log_debug(ret << " bytes read");
    setg(ibuffer, ibuffer, ibuffer + ret);

    return *gptr();
  }

  int Pipestreambuf::sync()
  {
    log_debug("sync()");
    if (pptr() != pbase())
    {
      char* p = pbase();
      while (p < pptr())
      {
        log_debug("write " << (pptr() - p) << " bytes to fd " << getWriteFd());
        ssize_t ret = ::write(getWriteFd(), p, pptr() - p);

        if(ret < 0)
          throw SystemError(errno, "write");

        if (ret == 0)
          return traits_type::eof();

        log_debug(ret << " bytes written to fd " << getWriteFd());
        p += ret;
      }
      setp(obuffer, obuffer + bufsize);
    }
    return 0;
  }

}
}
<commit_msg>fix reading binary data from pipestream: '\xff' was misinterpreted as eof<commit_after>/*
 * Copyright (C) 2007 Tommi Maekitalo
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * As a special exception, you may use this file as part of a free
 * software library without restriction. Specifically, if other files
 * instantiate templates or use macros or inline functions from this
 * file, or you compile this file and link it with other files to
 * produce an executable, this file does not by itself cause the
 * resulting executable to be covered by the GNU General Public
 * License. This exception does not however invalidate any other
 * reasons why the executable file might be covered by the GNU Library
 * General Public License.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 */

#include <cxxtools/posix/pipestream.h>
#include <cxxtools/systemerror.h>
#include <algorithm>
#include <unistd.h>
#include <cxxtools/log.h>
#include <cstring>
#include <errno.h>
#include <unistd.h>
#include <fcntl.h>

log_define("cxxtools.pipestream")

namespace cxxtools
{
namespace posix
{
  Pipestreambuf::Pipestreambuf(unsigned bufsize_)
    : bufsize(bufsize_),
      ibuffer(0),
      obuffer(0)
  { }

  Pipestreambuf::~Pipestreambuf()
  {
    log_debug("Pipestreambuf::~Pipestreambuf()");

    try
    {
      closeReadFd();
    }
    catch (const std::exception& e)
    {
      log_debug("ignore exception in closing read pipe: " << e.what());
    }

    try
    {
      closeWriteFd();
    }
    catch (const std::exception& e)
    {
      log_debug("ignore exception in closing write pipe: " << e.what());
    }

    delete [] ibuffer;
    delete [] obuffer;
  }

  std::streambuf::int_type Pipestreambuf::overflow(std::streambuf::int_type ch)
  {
    log_debug("overflow(" << ch << ')');

    if (pptr() != pbase())
    {
      log_debug("write " << (pptr() - pbase()) << " bytes to fd " << getWriteFd());
      ssize_t ret = ::write(getWriteFd(), pbase(), pptr() - pbase());

      if(ret < 0)
        throw SystemError(errno, "write");

      if (ret == 0)
        return traits_type::eof();
      else
      {
        log_debug(ret << " bytes written to fd " << getWriteFd());
        if (static_cast<unsigned>(ret) < bufsize)
          std::memmove(obuffer, obuffer + ret, bufsize - ret);
        setp(obuffer, obuffer + bufsize);
        pbump(bufsize - ret);
      }
    }
    else
    {
      log_debug("initialize outputbuffer");
      if (obuffer == 0)
      {
        log_debug("allocate " << bufsize << " bytes output buffer");
        obuffer = new char[bufsize];
      }

      setp(obuffer, obuffer + bufsize);
    }

    if (ch != traits_type::eof())
    {
      *pptr() = traits_type::to_char_type(ch);
      pbump(1);
    }

    return 0;
  }

  std::streambuf::int_type Pipestreambuf::underflow()
  {
    log_debug("underflow()");

    if (ibuffer == 0)
    {
      log_debug("allocate " << bufsize << " bytes input buffer");
      ibuffer = new char[bufsize];
    }

    log_debug("read from fd " << getReadFd());
    int ret = ::read(getReadFd(), ibuffer, bufsize);
    log_debug("read returned " << ret);

    if(ret < 0)
      throw SystemError(errno, "read");

    if (ret == 0)
      return traits_type::eof();

    log_debug(ret << " bytes read");
    setg(ibuffer, ibuffer, ibuffer + ret);

    return traits_type::to_int_type( *(gptr()) );
  }

  int Pipestreambuf::sync()
  {
    log_debug("sync()");
    if (pptr() != pbase())
    {
      char* p = pbase();
      while (p < pptr())
      {
        log_debug("write " << (pptr() - p) << " bytes to fd " << getWriteFd());
        ssize_t ret = ::write(getWriteFd(), p, pptr() - p);

        if(ret < 0)
          throw SystemError(errno, "write");

        if (ret == 0)
          return traits_type::eof();

        log_debug(ret << " bytes written to fd " << getWriteFd());
        p += ret;
      }
      setp(obuffer, obuffer + bufsize);
    }
    return 0;
  }

}
}
<|endoftext|>