<commit_before>#include "file.h"
#include "string.h"
#include <algorithm>
#include <cstdio>
#include <cstring>
#include <fstream>
#include <memory>
#include <stdexcept>
#include <string>

#if defined(WIN32) || defined(_WIN32) || defined(__WIN32) && !defined(__CYGWIN__)
#define PLATFORM_WINDOWS
#endif

#ifdef PLATFORM_WINDOWS
#include <direct.h>
#include <shlwapi.h>
#include <windows.h>
#else
#include <fcntl.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <unistd.h>
#endif

#ifdef PLATFORM_WINDOWS
// This is the only module that requires windows character conversion

std::string to_string(const wchar_t* str = L"")
{
    if (!str) {
        str = L"";
    }
    auto length = WideCharToMultiByte(CP_UTF8, 0, str, -1, nullptr, 0, nullptr, nullptr);
    std::unique_ptr<char[]> buffer(new char[length + 1]);
    WideCharToMultiByte(CP_UTF8, 0, str, -1, buffer.get(), length, nullptr, nullptr);

    return std::string(buffer.get());
}

// use unique_ptr to prevent memory leak with exceptions.
inline std::unique_ptr<wchar_t[]> to_wchar(const std::string str)
{
    auto length = MultiByteToWideChar(CP_UTF8, 0, str.c_str(), -1, nullptr, 0);
    std::unique_ptr<wchar_t[]> buffer(new wchar_t[length + 1]);
    MultiByteToWideChar(CP_UTF8, 0, str.c_str(), -1, buffer.get(), length);

    return std::move(buffer);
}
#endif

using namespace UnTech;

std::string File::readUtf8TextFile(const std::string& filename)
{
    std::ifstream in(filename, std::ios::in | std::ios::binary);
    if (in) {
        uint8_t bom[3];
        in.read((char*)bom, sizeof(bom));

        in.seekg(0, std::ios::end);
        size_t size = in.tellg();

        // check for BOM
        if (size > 3 && bom[0] == 0xEF && bom[1] == 0xBB && bom[2] == 0xBF) {
            in.seekg(3, std::ios::beg);
            size -= 3;
        }
        else {
            in.seekg(0, std::ios::beg);
        }

        std::string ret;
        ret.reserve(size + 1);

        ret.assign((std::istreambuf_iterator<char>(in)), (std::istreambuf_iterator<char>()));

        in.close();

        if (!String::checkUtf8WellFormed(ret)) {
            throw std::runtime_error("File is not UTF-8 Well Formed");
        }

        return (ret);
    }
    throw std::runtime_error("Cannot open file");
}

std::pair<std::string, std::string> File::splitFilename(const std::string& filename)
{
    if (filename.empty()) {
        return { std::string(), std::string() };
    }

#ifdef PLATFORM_WINDOWS
    // search both types of slash in windows.
    auto i = filename.find_last_of("\\/");
#else
    auto i = filename.find_last_of('/');
#endif

    if (i == std::string::npos) {
        return { std::string(), filename };
    }
    else {
        return { filename.substr(0, i + 1), filename.substr(i + 1) };
    }
}

std::string File::cwd()
{
#ifdef PLATFORM_WINDOWS
    wchar_t wpath[PATH_MAX] = L"";

    if (!_wgetcwd(wpath, PATH_MAX)) {
        throw std::runtime_error("Error getting working directory");
    }

    return to_string(wpath);

#else
    char* dir = get_current_dir_name();

    if (dir) {
        std::string ret(dir);
        free(dir);
        return ret;
    }
    else {
        throw std::runtime_error("Error with path");
    }
#endif
}

std::string File::cleanPath(const std::string& path)
{
    if (path.empty()) {
        return path;
    }

#ifdef PLATFORM_WINDOWS
    const char SEP = '\\';

    std::string newPath = path;
    std::replace(newPath.begin(), newPath.end(), '/', '\\');

    const char* source = newPath.c_str();

#else
    const char SEP = '/';

    const char* source = path.c_str();
#endif

    char ret[path.length() + 1];

    char* pos = ret;
    char* dirs[path.length() / 2];
    size_t nDirs = 0;

    while (*source != 0) {
        // Start of a directory/file

        if (source[0] == '.' && source[1] == SEP) {
            // ignore all . directories
            source += 2;
        }

        else if (source[0] == '.' && source[1] == '.' && source[2] == SEP) {
            if (nDirs > 1) {
                // shift pos to previous directory

                pos = dirs[nDirs - 2];
                nDirs--;
                source += 3;
            }
            else {
                pos[0] = source[0];
                pos[1] = source[1];
                pos[2] = source[2];
                pos += 3;
                source += 3;
            }
        }

        else {
            // loop until end of directory separator

            while (*source != 0 && *source != SEP) {
                *(pos++) = *(source++);
            }

            // include terminator ('/' or '\0').
            *(pos++) = *(source++);

            if (*(source - 1) == 0) {
                break;
            }
            else if (*(source - 1) == SEP) {
                // remember previous dir position
                dirs[nDirs] = pos;
                nDirs++;
            }
        }

#ifndef PLATFORM_WINDOWS
        // ignore duplicate separators
        while (*source == SEP) {
            source++;
        }
#endif
    }

    return std::string(ret);
}

std::string File::joinPath(const std::string& dir, const std::string& path)
{
#ifdef PLATFORM_WINDOWS
    auto wdir = to_wchar(dir);
    auto wpath = to_wchar(path);
    wchar_t wjoined[PATH_MAX] = L"";

    PathCombineW(wjoined, wdir.get(), wpath.get());

    return to_string(wjoined);

#else
    if (path.front() == '/') {
        return path;
    }

    std::string join;
    join.reserve(dir.length() + path.length() + 1);

    if (!dir.empty() && dir.back() != '/') {
        join = dir + '/' + path;
    }
    else {
        join = dir + path;
    }

    return cleanPath(join);
#endif
}

std::string File::fullPath(const std::string& path)
{
#ifdef PLATFORM_WINDOWS
    wchar_t wfullpath[PATH_MAX] = L"";
    auto wpath = to_wchar(path);

    if (!_wfullpath(wfullpath, wpath.get(), PATH_MAX)) {
        throw std::runtime_error("Error expanding path");
    }

    return to_string(wfullpath);

#else
    if (path.front() == '/') {
        return path;
    }

    return joinPath(cwd(), path);
#endif
}

std::string File::relativePath(const std::string& sourceDir, const std::string& destPath)
{
    if (sourceDir.empty()) {
        return File::fullPath(destPath);
    }

#ifdef PLATFORM_WINDOWS
    wchar_t wrelpath[PATH_MAX] = L"";
    auto wsource = to_wchar(sourceDir);
    auto wdest = to_wchar(destPath);

    if (!PathRelativePathToW(wrelpath, wsource.get(), FILE_ATTRIBUTE_DIRECTORY, wdest.get(), FILE_ATTRIBUTE_NORMAL)) {
        throw std::runtime_error("Error expanding path");
    }

    return to_string(wrelpath);

#else
    std::string source = File::fullPath(sourceDir);
    std::string dest = File::fullPath(destPath);

    if (source.empty() || dest.empty()
        || source.front() != '/' || dest.front() != '/') {

        return dest;
    }

    std::string ret;

    // Get common root directories
    size_t lastCommonSep = 0;
    {
        size_t i = 0;
        while (i < source.size() && i < dest.size() && source[i] == dest[i]) {
            if (source[i] == '/') {
                lastCommonSep = i;
            }
            i++;
        }

        if (i == source.size() && dest.size() > source.size() && dest[i] == '/') {
            lastCommonSep = i;
        }
    }

    if (source.size() > lastCommonSep) {
        size_t p = lastCommonSep + 1;

        while ((p = source.find('/', p)) != std::string::npos) {
            ret += "../";
            p += 1;
        }
    }

    ret += dest.substr(lastCommonSep + 1);

    return ret;
#endif
}

#ifdef PLATFORM_WINDOWS
void File::atomicWrite(const std::string& filename, const void* data, size_t size)
{
    const size_t BLOCK_SIZE = 4096;

    // atomically creating a tempfile name is hard
    // just simplify it by using a ~ temp file instead.
    auto tmpFilename = to_wchar(filename + "~");

    // open file normally.
    // error out if file exists.
    HANDLE hFile = CreateFileW(tmpFilename.get(), GENERIC_WRITE, 0, nullptr,
                               CREATE_NEW, FILE_ATTRIBUTE_NORMAL, nullptr);

    if (hFile == INVALID_HANDLE_VALUE) {
        if (GetLastError() == ERROR_FILE_EXISTS) {
            throw std::runtime_error("Temporary file already exists: " + filename + "~");
        }
        else {
            throw std::runtime_error("Cannot open temporary file " + filename + "~");
        }
    }

    const uint8_t* ptr = static_cast<const uint8_t*>(data);
    size_t todo = size;

    while (todo > 0) {
        DWORD toWrite = std::min(BLOCK_SIZE, todo);
        DWORD written = 0;

        auto ret = WriteFile(hFile, ptr, toWrite, &written, NULL);

        if (ret == FALSE || written != toWrite) {
            CloseHandle(hFile);
            throw std::runtime_error("Error writing file");
        }

        ptr += toWrite;
        todo -= toWrite;
    }

    CloseHandle(hFile);

    auto wFilename = to_wchar(filename);
    bool s = MoveFileExW(tmpFilename.get(), wFilename.get(),
                         MOVEFILE_REPLACE_EXISTING | MOVEFILE_COPY_ALLOWED);
    if (!s) {
        throw std::runtime_error("MoveFileEx failed");
    }
}
#else
void File::atomicWrite(const std::string& filename, const void* data, size_t size)
{
    const size_t BLOCK_SIZE = 4096;

    int fd;
    char tmpFilename[filename.size() + 12];
    strncpy(tmpFilename, filename.c_str(), filename.size() + 1);
    strncpy(tmpFilename + filename.size(), "-tmpXXXXXX", 12);

    struct stat statbuf;
    if (stat(filename.c_str(), &statbuf) == 0) {
        if (S_ISLNK(statbuf.st_mode)) {
            throw std::runtime_error("Cannot write to a symbolic link");
        }

        // check if we can write to file
        bool canWrite = ((getuid() == statbuf.st_uid) && (statbuf.st_mode & S_IWUSR))
                        || ((getgid() == statbuf.st_gid) && (statbuf.st_mode & S_IWGRP))
                        || (statbuf.st_mode & S_IWOTH);
        if (!canWrite) {
            throw std::runtime_error("User can not write to " + filename);
        }

        fd = mkostemp(tmpFilename, O_WRONLY);
        if (fd < 0) {
            throw std::system_error(errno, std::system_category());
        }

        // mkostemp sets file permission to 0600
        // Set the permissions to match filename
        chmod(tmpFilename, statbuf.st_mode);
        chown(tmpFilename, statbuf.st_uid, statbuf.st_gid);
    }
    else {
        fd = mkostemp(tmpFilename, O_WRONLY);
        if (fd < 0) {
            throw std::system_error(errno, std::system_category());
        }

        // mkostemp sets file permission to 0600
        // Set the permissions what user would expect
        mode_t mask = umask(0);
        umask(mask);
        chmod(tmpFilename, 0666 & ~mask);
    }

    const uint8_t* ptr = static_cast<const uint8_t*>(data);
    size_t todo = size;
    ssize_t done;

    while (todo > 0) {
        done = ::write(fd, ptr, std::min(BLOCK_SIZE, todo));
        if (done < 0) {
            auto err = errno;
            close(fd);
            throw std::system_error(err, std::system_category());
        }

        ptr += done;
        todo -= done;
    }

    int r;
    r = close(fd);
    if (r != 0) {
        throw std::system_error(errno, std::system_category());
    }

    r = rename(tmpFilename, filename.c_str());
    if (r != 0) {
        throw std::system_error(errno, std::system_category());
    }
}
#endif
<commit_msg>fix: make File::relativePath consistent between windows/unix<commit_after>#include "file.h"
#include "string.h"
#include <algorithm>
#include <cstdio>
#include <cstring>
#include <fstream>
#include <memory>
#include <stdexcept>
#include <string>

#if defined(WIN32) || defined(_WIN32) || defined(__WIN32) && !defined(__CYGWIN__)
#define PLATFORM_WINDOWS
#endif

#ifdef PLATFORM_WINDOWS
#include <direct.h>
#include <shlwapi.h>
#include <windows.h>
#else
#include <fcntl.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <unistd.h>
#endif

#ifdef PLATFORM_WINDOWS
// This is the only module that requires windows character conversion

std::string to_string(const wchar_t* str = L"")
{
    if (!str) {
        str = L"";
    }
    auto length = WideCharToMultiByte(CP_UTF8, 0, str, -1, nullptr, 0, nullptr, nullptr);
    std::unique_ptr<char[]> buffer(new char[length + 1]);
    WideCharToMultiByte(CP_UTF8, 0, str, -1, buffer.get(), length, nullptr, nullptr);

    return std::string(buffer.get());
}

// use unique_ptr to prevent memory leak with exceptions.
inline std::unique_ptr<wchar_t[]> to_wchar(const std::string str)
{
    auto length = MultiByteToWideChar(CP_UTF8, 0, str.c_str(), -1, nullptr, 0);
    std::unique_ptr<wchar_t[]> buffer(new wchar_t[length + 1]);
    MultiByteToWideChar(CP_UTF8, 0, str.c_str(), -1, buffer.get(), length);

    return std::move(buffer);
}
#endif

using namespace UnTech;

std::string File::readUtf8TextFile(const std::string& filename)
{
    std::ifstream in(filename, std::ios::in | std::ios::binary);
    if (in) {
        uint8_t bom[3];
        in.read((char*)bom, sizeof(bom));

        in.seekg(0, std::ios::end);
        size_t size = in.tellg();

        // check for BOM
        if (size > 3 && bom[0] == 0xEF && bom[1] == 0xBB && bom[2] == 0xBF) {
            in.seekg(3, std::ios::beg);
            size -= 3;
        }
        else {
            in.seekg(0, std::ios::beg);
        }

        std::string ret;
        ret.reserve(size + 1);

        ret.assign((std::istreambuf_iterator<char>(in)), (std::istreambuf_iterator<char>()));

        in.close();

        if (!String::checkUtf8WellFormed(ret)) {
            throw std::runtime_error("File is not UTF-8 Well Formed");
        }

        return (ret);
    }
    throw std::runtime_error("Cannot open file");
}

std::pair<std::string, std::string> File::splitFilename(const std::string& filename)
{
    if (filename.empty()) {
        return { std::string(), std::string() };
    }

#ifdef PLATFORM_WINDOWS
    // search both types of slash in windows.
    auto i = filename.find_last_of("\\/");
#else
    auto i = filename.find_last_of('/');
#endif

    if (i == std::string::npos) {
        return { std::string(), filename };
    }
    else {
        return { filename.substr(0, i + 1), filename.substr(i + 1) };
    }
}

std::string File::cwd()
{
#ifdef PLATFORM_WINDOWS
    wchar_t wpath[PATH_MAX] = L"";

    if (!_wgetcwd(wpath, PATH_MAX)) {
        throw std::runtime_error("Error getting working directory");
    }

    return to_string(wpath);

#else
    char* dir = get_current_dir_name();

    if (dir) {
        std::string ret(dir);
        free(dir);
        return ret;
    }
    else {
        throw std::runtime_error("Error with path");
    }
#endif
}

std::string File::cleanPath(const std::string& path)
{
    if (path.empty()) {
        return path;
    }

#ifdef PLATFORM_WINDOWS
    const char SEP = '\\';

    std::string newPath = path;
    std::replace(newPath.begin(), newPath.end(), '/', '\\');

    const char* source = newPath.c_str();

#else
    const char SEP = '/';

    const char* source = path.c_str();
#endif

    char ret[path.length() + 1];

    char* pos = ret;
    char* dirs[path.length() / 2];
    size_t nDirs = 0;

    while (*source != 0) {
        // Start of a directory/file

        if (source[0] == '.' && source[1] == SEP) {
            // ignore all . directories
            source += 2;
        }

        else if (source[0] == '.' && source[1] == '.' && source[2] == SEP) {
            if (nDirs > 1) {
                // shift pos to previous directory

                pos = dirs[nDirs - 2];
                nDirs--;
                source += 3;
            }
            else {
                pos[0] = source[0];
                pos[1] = source[1];
                pos[2] = source[2];
                pos += 3;
                source += 3;
            }
        }

        else {
            // loop until end of directory separator

            while (*source != 0 && *source != SEP) {
                *(pos++) = *(source++);
            }

            // include terminator ('/' or '\0').
            *(pos++) = *(source++);

            if (*(source - 1) == 0) {
                break;
            }
            else if (*(source - 1) == SEP) {
                // remember previous dir position
                dirs[nDirs] = pos;
                nDirs++;
            }
        }

#ifndef PLATFORM_WINDOWS
        // ignore duplicate separators
        while (*source == SEP) {
            source++;
        }
#endif
    }

    return std::string(ret);
}

std::string File::joinPath(const std::string& dir, const std::string& path)
{
#ifdef PLATFORM_WINDOWS
    auto wdir = to_wchar(dir);
    auto wpath = to_wchar(path);
    wchar_t wjoined[PATH_MAX] = L"";

    PathCombineW(wjoined, wdir.get(), wpath.get());

    return to_string(wjoined);

#else
    if (path.front() == '/') {
        return path;
    }

    std::string join;
    join.reserve(dir.length() + path.length() + 1);

    if (!dir.empty() && dir.back() != '/') {
        join = dir + '/' + path;
    }
    else {
        join = dir + path;
    }

    return cleanPath(join);
#endif
}

std::string File::fullPath(const std::string& path)
{
#ifdef PLATFORM_WINDOWS
    wchar_t wfullpath[PATH_MAX] = L"";
    auto wpath = to_wchar(path);

    if (!_wfullpath(wfullpath, wpath.get(), PATH_MAX)) {
        throw std::runtime_error("Error expanding path");
    }

    return to_string(wfullpath);

#else
    if (path.front() == '/') {
        return path;
    }

    return joinPath(cwd(), path);
#endif
}

std::string File::relativePath(const std::string& sourceDir, const std::string& destPath)
{
    if (sourceDir.empty()) {
        return File::fullPath(destPath);
    }

#ifdef PLATFORM_WINDOWS
    wchar_t wrelpath[PATH_MAX] = L"";
    auto wsource = to_wchar(sourceDir);
    auto wdest = to_wchar(destPath);

    if (!PathRelativePathToW(wrelpath, wsource.get(), FILE_ATTRIBUTE_DIRECTORY, wdest.get(), FILE_ATTRIBUTE_NORMAL)) {
        return File::fullPath(destPath);
    }

    if (wrelpath[0] != L'\\') {
        return to_string(wrelpath);
    }
    else {
        return to_string(wrelpath + 1);
    }

#else

    std::string source = File::fullPath(sourceDir);
    std::string dest = File::fullPath(destPath);

    if (source.empty() || dest.empty()
        || source.front() != '/' || dest.front() != '/') {

        return dest;
    }

    std::string ret;

    // Get common root directories
    size_t lastCommonSep = 0;
    {
        size_t i = 0;
        while (i < source.size() && i < dest.size() && source[i] == dest[i]) {
            if (source[i] == '/') {
                lastCommonSep = i;
            }
            i++;
        }

        if (i == source.size() && dest.size() > source.size() && dest[i] == '/') {
            lastCommonSep = i;
        }
    }

    if (source.size() > lastCommonSep) {
        size_t p = lastCommonSep + 1;

        while ((p = source.find('/', p)) != std::string::npos) {
            ret += "../";
            p += 1;
        }
    }

    ret += dest.substr(lastCommonSep + 1);

    return ret;
#endif
}

#ifdef PLATFORM_WINDOWS
void File::atomicWrite(const std::string& filename, const void* data, size_t size)
{
    const size_t BLOCK_SIZE = 4096;

    // atomically creating a tempfile name is hard
    // just simplify it by using a ~ temp file instead.
    auto tmpFilename = to_wchar(filename + "~");

    // open file normally.
    // error out if file exists.
    HANDLE hFile = CreateFileW(tmpFilename.get(), GENERIC_WRITE, 0, nullptr,
                               CREATE_NEW, FILE_ATTRIBUTE_NORMAL, nullptr);

    if (hFile == INVALID_HANDLE_VALUE) {
        if (GetLastError() == ERROR_FILE_EXISTS) {
            throw std::runtime_error("Temporary file already exists: " + filename + "~");
        }
        else {
            throw std::runtime_error("Cannot open temporary file " + filename + "~");
        }
    }

    const uint8_t* ptr = static_cast<const uint8_t*>(data);
    size_t todo = size;

    while (todo > 0) {
        DWORD toWrite = std::min(BLOCK_SIZE, todo);
        DWORD written = 0;

        auto ret = WriteFile(hFile, ptr, toWrite, &written, NULL);

        if (ret == FALSE || written != toWrite) {
            CloseHandle(hFile);
            throw std::runtime_error("Error writing file");
        }

        ptr += toWrite;
        todo -= toWrite;
    }

    CloseHandle(hFile);

    auto wFilename = to_wchar(filename);
    bool s = MoveFileExW(tmpFilename.get(), wFilename.get(),
                         MOVEFILE_REPLACE_EXISTING | MOVEFILE_COPY_ALLOWED);
    if (!s) {
        throw std::runtime_error("MoveFileEx failed");
    }
}
#else
void File::atomicWrite(const std::string& filename, const void* data, size_t size)
{
    const size_t BLOCK_SIZE = 4096;

    int fd;
    char tmpFilename[filename.size() + 12];
    strncpy(tmpFilename, filename.c_str(), filename.size() + 1);
    strncpy(tmpFilename + filename.size(), "-tmpXXXXXX", 12);

    struct stat statbuf;
    if (stat(filename.c_str(), &statbuf) == 0) {
        if (S_ISLNK(statbuf.st_mode)) {
            throw std::runtime_error("Cannot write to a symbolic link");
        }

        // check if we can write to file
        bool canWrite = ((getuid() == statbuf.st_uid) && (statbuf.st_mode & S_IWUSR))
                        || ((getgid() == statbuf.st_gid) && (statbuf.st_mode & S_IWGRP))
                        || (statbuf.st_mode & S_IWOTH);
        if (!canWrite) {
            throw std::runtime_error("User can not write to " + filename);
        }

        fd = mkostemp(tmpFilename, O_WRONLY);
        if (fd < 0) {
            throw std::system_error(errno, std::system_category());
        }

        // mkostemp sets file permission to 0600
        // Set the permissions to match filename
        chmod(tmpFilename, statbuf.st_mode);
        chown(tmpFilename, statbuf.st_uid, statbuf.st_gid);
    }
    else {
        fd = mkostemp(tmpFilename, O_WRONLY);
        if (fd < 0) {
            throw std::system_error(errno, std::system_category());
        }

        // mkostemp sets file permission to 0600
        // Set the permissions what user would expect
        mode_t mask = umask(0);
        umask(mask);
        chmod(tmpFilename, 0666 & ~mask);
    }

    const uint8_t* ptr = static_cast<const uint8_t*>(data);
    size_t todo = size;
    ssize_t done;

    while (todo > 0) {
        done = ::write(fd, ptr, std::min(BLOCK_SIZE, todo));
        if (done < 0) {
            auto err = errno;
            close(fd);
            throw std::system_error(err, std::system_category());
        }

        ptr += done;
        todo -= done;
    }

    int r;
    r = close(fd);
    if (r != 0) {
        throw std::system_error(errno, std::system_category());
    }

    r = rename(tmpFilename, filename.c_str());
    if (r != 0) {
        throw std::system_error(errno, std::system_category());
    }
}
#endif
<|endoftext|>


<commit_before>//
//  JustInTime.hpp
//  Clock Signal
//
//  Created by Thomas Harte on 28/07/2019.
//  Copyright © 2019 Thomas Harte. All rights reserved.
//

#ifndef JustInTime_h
#define JustInTime_h

#include "../Concurrency/AsyncTaskQueue.hpp"
#include "ClockingHintSource.hpp"
#include "ForceInline.hpp"

/*!
	A JustInTimeActor holds (i) an embedded object with a run_for method; and (ii) an amount
	of time since run_for was last called.

	Time can be added using the += operator. The -> operator can be used to access the
	embedded object. All time accumulated will be pushed to object before the pointer is returned.

	Machines that accumulate HalfCycle time but supply to a Cycle-counted device may supply a
	separate @c TargetTimeScale at template declaration.

	If the held object implements get_next_sequence_point() then it'll be used to flush implicitly
	as and when sequence points are hit. Callers can use will_flush() to predict these.

	If the held object is a subclass of ClockingHint::Source, this template will register as an
	observer and potentially stop clocking or stop delaying clocking until just-in-time references
	as directed.
*/
template <class T, int multiplier = 1, int divider = 1, class LocalTimeScale = HalfCycles, class TargetTimeScale = LocalTimeScale> class JustInTimeActor:
	public ClockingHint::Observer {
	private:
		class SequencePointAwareDeleter {
			public:
				explicit SequencePointAwareDeleter(JustInTimeActor<T, multiplier, divider, LocalTimeScale, TargetTimeScale> *actor) : actor_(actor) {}

				void operator ()(const T *const) const {
					if constexpr (has_sequence_points<T>::value) {
						actor_->update_sequence_point();
					}
				}

			private:
				JustInTimeActor<T, multiplier, divider, LocalTimeScale, TargetTimeScale> *const actor_;
		};

	public:
		/// Constructs a new JustInTimeActor using the same construction arguments as the included object.
		template<typename... Args> JustInTimeActor(Args&&... args) : object_(std::forward<Args>(args)...) {
			if constexpr (std::is_base_of<ClockingHint::Source, T>::value) {
				object_.set_clocking_hint_observer(this);
			}
		}

		/// Adds time to the actor.
		forceinline void operator += (LocalTimeScale rhs) {
			if constexpr (std::is_base_of<ClockingHint::Source, T>::value) {
				if(clocking_preference_ == ClockingHint::Preference::None) {
					return;
				}
			}

			if constexpr (multiplier != 1) {
				time_since_update_ += rhs * multiplier;
			} else {
				time_since_update_ += rhs;
			}
			is_flushed_ = false;

			if constexpr (std::is_base_of<ClockingHint::Source, T>::value) {
				if (clocking_preference_ == ClockingHint::Preference::RealTime) {
					flush();
					return;
				}
			}

			if constexpr (has_sequence_points<T>::value) {
				time_until_event_ -= rhs;
				if(time_until_event_ <= LocalTimeScale(0)) {
					flush();
				}
			}
		}

		/// Flushes all accumulated time and returns a pointer to the included object.
		///
		/// If this object provides sequence points, checks for changes to the next
		/// sequence point upon deletion of the pointer.
		forceinline auto operator->() {
			flush();
			return std::unique_ptr<T, SequencePointAwareDeleter>(&object_, SequencePointAwareDeleter(this));
		}

		/// Acts exactly as per the standard ->, but preserves constness.
		///
		/// Despite being const, this will flush the object and, if relevant, update the next sequence point.
		forceinline auto operator -> () const {
			auto non_const_this = const_cast<JustInTimeActor<T, multiplier, divider, LocalTimeScale, TargetTimeScale> *>(this);
			non_const_this->flush();
			return std::unique_ptr<const T, SequencePointAwareDeleter>(&object_, SequencePointAwareDeleter(non_const_this));
		}

		/// @returns a pointer to the included object, without flushing time.
		forceinline T *last_valid() {
			return &object_;
		}

		/// @returns the amount of time since the object was last flushed, in the target time scale.
		forceinline TargetTimeScale time_since_flush() const {
			// TODO: does this handle conversions properly where TargetTimeScale != LocalTimeScale?
			if constexpr (divider == 1) {
				return time_since_update_;
			}
			return TargetTimeScale(time_since_update_.as_integral() / divider);
		}

		/// Flushes all accumulated time.
		///
		/// This does not affect this actor's record of when the next sequence point will occur.
		forceinline void flush() {
			if(!is_flushed_) {
				did_flush_ = is_flushed_ = true;
				if constexpr (divider == 1) {
					const auto duration = time_since_update_.template flush<TargetTimeScale>();
					object_.run_for(duration);
				} else {
					const auto duration = time_since_update_.template divide<TargetTimeScale>(LocalTimeScale(divider));
					if(duration > TargetTimeScale(0))
						object_.run_for(duration);
				}
			}
		}

		/// Indicates whether a flush has occurred since the last call to did_flush().
		forceinline bool did_flush() {
			const bool did_flush = did_flush_;
			did_flush_ = false;
			return did_flush;
		}

		/// @returns the number of cycles until the next sequence-point-based flush, if the embedded object
		/// supports sequence points; @c LocalTimeScale() otherwise.
		LocalTimeScale cycles_until_implicit_flush() const {
			return time_until_event_;
		}

		/// Indicates whether a sequence-point-caused flush will occur if the specified period is added.
		forceinline bool will_flush(LocalTimeScale rhs) const {
			if constexpr (!has_sequence_points<T>::value) {
				return false;
			}
			return rhs >= time_until_event_;
		}

		/// Updates this template's record of the next sequence point.
		void update_sequence_point() {
			if constexpr (has_sequence_points<T>::value) {
				time_until_event_ = object_.get_next_sequence_point();
				assert(time_until_event_ > LocalTimeScale(0));
			}
		}

	private:
		T object_;
		LocalTimeScale time_since_update_, time_until_event_;
		bool is_flushed_ = true;
		bool did_flush_ = false;

		template <typename S, typename = void> struct has_sequence_points : std::false_type {};
		template <typename S> struct has_sequence_points<S, decltype(void(std::declval<S &>().get_next_sequence_point()))> : std::true_type {};

		ClockingHint::Preference clocking_preference_ = ClockingHint::Preference::JustInTime;
		void set_component_prefers_clocking(ClockingHint::Source *, ClockingHint::Preference clocking) {
			clocking_preference_ = clocking;
		}
};

/*!
	A RealTimeActor presents the same interface as a JustInTimeActor but doesn't defer work.
	Time added will be performed immediately.

	Its primary purpose is to allow consumers to remain flexible in their scheduling.
*/
template <class T, int multiplier = 1, int divider = 1, class LocalTimeScale = HalfCycles, class TargetTimeScale = LocalTimeScale> class RealTimeActor {
	public:
		template<typename... Args> RealTimeActor(Args&&... args) : object_(std::forward<Args>(args)...) {}

		forceinline void operator += (const LocalTimeScale &rhs) {
			if constexpr (multiplier == 1 && divider == 1) {
				object_.run_for(TargetTimeScale(rhs));
				return;
			}

			if constexpr (multiplier == 1) {
				accumulated_time_ += rhs;
			} else {
				accumulated_time_ += rhs * multiplier;
			}

			if constexpr (divider == 1) {
				const auto duration = accumulated_time_.template flush<TargetTimeScale>();
				object_.run_for(duration);
			} else {
				const auto duration = accumulated_time_.template divide<TargetTimeScale>(LocalTimeScale(divider));
				if(duration > TargetTimeScale(0))
					object_.run_for(duration);
			}
		}

		forceinline T *operator->()				{	return &object_;	}
		forceinline const T *operator->() const	{	return &object_;	}
		forceinline T *last_valid() 			{	return &object_;	}
		forceinline void flush()				{}

	private:
		T object_;
		LocalTimeScale accumulated_time_;
};

/*!
	A AsyncJustInTimeActor acts like a JustInTimeActor but additionally contains an AsyncTaskQueue.
	Any time the amount of accumulated time crosses a threshold provided at construction time,
	the object will be updated on the AsyncTaskQueue.
*/
template <class T, class LocalTimeScale = HalfCycles, class TargetTimeScale = LocalTimeScale> class AsyncJustInTimeActor {
	public:
		/// Constructs a new AsyncJustInTimeActor using the same construction arguments as the included object.
		template<typename... Args> AsyncJustInTimeActor(TargetTimeScale threshold, Args&&... args) :
			object_(std::forward<Args>(args)...),
		 	threshold_(threshold) {}

		/// Adds time to the actor.
		inline void operator += (const LocalTimeScale &rhs) {
			time_since_update_ += rhs;
			if(time_since_update_ >= threshold_) {
				time_since_update_ -= threshold_;
				task_queue_.enqueue([this] () {
					object_.run_for(threshold_);
				});
			}
			is_flushed_ = false;
		}

		/// Flushes all accumulated time and returns a pointer to the included object.
		inline T *operator->() {
			flush();
			return &object_;
		}

		/// Returns a pointer to the included object without flushing time.
		inline T *last_valid() {
			return &object_;
		}

		/// Flushes all accumulated time.
		inline void flush() {
			if(!is_flushed_) {
				task_queue_.flush();
				object_.run_for(time_since_update_.template flush<TargetTimeScale>());
				is_flushed_ = true;
			}
		}

	private:
		T object_;
		LocalTimeScale time_since_update_;
		TargetTimeScale threshold_;
		bool is_flushed_ = true;
		Concurrency::AsyncTaskQueue task_queue_;
};

#endif /* JustInTime_h */
<commit_msg>Eliminates unused RealTimeActor, provides more feedback from +=, gets specific as to `nodiscard`s.<commit_after>//
//  JustInTime.hpp
//  Clock Signal
//
//  Created by Thomas Harte on 28/07/2019.
//  Copyright © 2019 Thomas Harte. All rights reserved.
//

#ifndef JustInTime_h
#define JustInTime_h

#include "../Concurrency/AsyncTaskQueue.hpp"
#include "ClockingHintSource.hpp"
#include "ForceInline.hpp"

/*!
	A JustInTimeActor holds (i) an embedded object with a run_for method; and (ii) an amount
	of time since run_for was last called.

	Time can be added using the += operator. The -> operator can be used to access the
	embedded object. All time accumulated will be pushed to object before the pointer is returned.

	Machines that accumulate HalfCycle time but supply to a Cycle-counted device may supply a
	separate @c TargetTimeScale at template declaration.

	If the held object implements get_next_sequence_point() then it'll be used to flush implicitly
	as and when sequence points are hit. Callers can use will_flush() to predict these.

	If the held object is a subclass of ClockingHint::Source, this template will register as an
	observer and potentially stop clocking or stop delaying clocking until just-in-time references
	as directed.
*/
template <class T, int multiplier = 1, int divider = 1, class LocalTimeScale = HalfCycles, class TargetTimeScale = LocalTimeScale> class JustInTimeActor:
	public ClockingHint::Observer {
	private:
		class SequencePointAwareDeleter {
			public:
				explicit SequencePointAwareDeleter(JustInTimeActor<T, multiplier, divider, LocalTimeScale, TargetTimeScale> *actor) : actor_(actor) {}

				void operator ()(const T *const) const {
					if constexpr (has_sequence_points<T>::value) {
						actor_->update_sequence_point();
					}
				}

			private:
				JustInTimeActor<T, multiplier, divider, LocalTimeScale, TargetTimeScale> *const actor_;
		};

	public:
		/// Constructs a new JustInTimeActor using the same construction arguments as the included object.
		template<typename... Args> JustInTimeActor(Args&&... args) : object_(std::forward<Args>(args)...) {
			if constexpr (std::is_base_of<ClockingHint::Source, T>::value) {
				object_.set_clocking_hint_observer(this);
			}
		}

		/// Adds time to the actor.
		///
		/// @returns @c true if adding time caused a flush; @c false otherwise.
		forceinline bool operator += (LocalTimeScale rhs) {
			if constexpr (std::is_base_of<ClockingHint::Source, T>::value) {
				if(clocking_preference_ == ClockingHint::Preference::None) {
					return false;
				}
			}

			if constexpr (multiplier != 1) {
				time_since_update_ += rhs * multiplier;
			} else {
				time_since_update_ += rhs;
			}
			is_flushed_ = false;

			if constexpr (std::is_base_of<ClockingHint::Source, T>::value) {
				if (clocking_preference_ == ClockingHint::Preference::RealTime) {
					flush();
					return true;
				}
			}

			if constexpr (has_sequence_points<T>::value) {
				time_until_event_ -= rhs;
				if(time_until_event_ <= LocalTimeScale(0)) {
					flush();
					return true;
				}
			}

			return false;
		}

		/// Flushes all accumulated time and returns a pointer to the included object.
		///
		/// If this object provides sequence points, checks for changes to the next
		/// sequence point upon deletion of the pointer.
		[[nodiscard]] forceinline auto operator->() {
			flush();
			return std::unique_ptr<T, SequencePointAwareDeleter>(&object_, SequencePointAwareDeleter(this));
		}

		/// Acts exactly as per the standard ->, but preserves constness.
		///
		/// Despite being const, this will flush the object and, if relevant, update the next sequence point.
		[[nodiscard]] forceinline auto operator -> () const {
			auto non_const_this = const_cast<JustInTimeActor<T, multiplier, divider, LocalTimeScale, TargetTimeScale> *>(this);
			non_const_this->flush();
			return std::unique_ptr<const T, SequencePointAwareDeleter>(&object_, SequencePointAwareDeleter(non_const_this));
		}

		/// @returns a pointer to the included object, without flushing time.
		[[nodiscard]] forceinline T *last_valid() {
			return &object_;
		}

		/// @returns the amount of time since the object was last flushed, in the target time scale.
		[[nodiscard]] forceinline TargetTimeScale time_since_flush() const {
			// TODO: does this handle conversions properly where TargetTimeScale != LocalTimeScale?
			if constexpr (divider == 1) {
				return time_since_update_;
			}
			return TargetTimeScale(time_since_update_.as_integral() / divider);
		}

		/// Flushes all accumulated time.
		///
		/// This does not affect this actor's record of when the next sequence point will occur.
		forceinline void flush() {
			if(!is_flushed_) {
				did_flush_ = is_flushed_ = true;
				if constexpr (divider == 1) {
					const auto duration = time_since_update_.template flush<TargetTimeScale>();
					object_.run_for(duration);
				} else {
					const auto duration = time_since_update_.template divide<TargetTimeScale>(LocalTimeScale(divider));
					if(duration > TargetTimeScale(0))
						object_.run_for(duration);
				}
			}
		}

		/// Indicates whether a flush has occurred since the last call to did_flush().
		[[nodiscard]] forceinline bool did_flush() {
			const bool did_flush = did_flush_;
			did_flush_ = false;
			return did_flush;
		}

		/// @returns the number of cycles until the next sequence-point-based flush, if the embedded object
		/// supports sequence points; @c LocalTimeScale() otherwise.
		[[nodiscard]] LocalTimeScale cycles_until_implicit_flush() const {
			return time_until_event_;
		}

		/// Indicates whether a sequence-point-caused flush will occur if the specified period is added.
		[[nodiscard]] forceinline bool will_flush(LocalTimeScale rhs) const {
			if constexpr (!has_sequence_points<T>::value) {
				return false;
			}
			return rhs >= time_until_event_;
		}

		/// Updates this template's record of the next sequence point.
		void update_sequence_point() {
			if constexpr (has_sequence_points<T>::value) {
				time_until_event_ = object_.get_next_sequence_point();
				assert(time_until_event_ > LocalTimeScale(0));
			}
		}

	private:
		T object_;
		LocalTimeScale time_since_update_, time_until_event_;
		bool is_flushed_ = true;
		bool did_flush_ = false;

		template <typename S, typename = void> struct has_sequence_points : std::false_type {};
		template <typename S> struct has_sequence_points<S, decltype(void(std::declval<S &>().get_next_sequence_point()))> : std::true_type {};

		ClockingHint::Preference clocking_preference_ = ClockingHint::Preference::JustInTime;
		void set_component_prefers_clocking(ClockingHint::Source *, ClockingHint::Preference clocking) {
			clocking_preference_ = clocking;
		}
};

/*!
	A AsyncJustInTimeActor acts like a JustInTimeActor but additionally contains an AsyncTaskQueue.
	Any time the amount of accumulated time crosses a threshold provided at construction time,
	the object will be updated on the AsyncTaskQueue.
*/
template <class T, class LocalTimeScale = HalfCycles, class TargetTimeScale = LocalTimeScale> class AsyncJustInTimeActor {
	public:
		/// Constructs a new AsyncJustInTimeActor using the same construction arguments as the included object.
		template<typename... Args> AsyncJustInTimeActor(TargetTimeScale threshold, Args&&... args) :
			object_(std::forward<Args>(args)...),
		 	threshold_(threshold) {}

		/// Adds time to the actor.
		inline void operator += (const LocalTimeScale &rhs) {
			time_since_update_ += rhs;
			if(time_since_update_ >= threshold_) {
				time_since_update_ -= threshold_;
				task_queue_.enqueue([this] () {
					object_.run_for(threshold_);
				});
			}
			is_flushed_ = false;
		}

		/// Flushes all accumulated time and returns a pointer to the included object.
		inline T *operator->() {
			flush();
			return &object_;
		}

		/// Returns a pointer to the included object without flushing time.
		inline T *last_valid() {
			return &object_;
		}

		/// Flushes all accumulated time.
		inline void flush() {
			if(!is_flushed_) {
				task_queue_.flush();
				object_.run_for(time_since_update_.template flush<TargetTimeScale>());
				is_flushed_ = true;
			}
		}

	private:
		T object_;
		LocalTimeScale time_since_update_;
		TargetTimeScale threshold_;
		bool is_flushed_ = true;
		Concurrency::AsyncTaskQueue task_queue_;
};

#endif /* JustInTime_h */
<|endoftext|>


<commit_before>/*
 * qmqtt_network.cpp - qmqtt network
 *
 * Copyright (c) 2013  Ery Lee <ery.lee at gmail dot com>
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 *   * Redistributions of source code must retain the above copyright notice,
 *     this list of conditions and the following disclaimer.
 *   * Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution.
 *   * Neither the name of mqttc nor the names of its contributors may be used
 *     to endorse or promote products derived from this software without
 *     specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 *
 */
#include <QDataStream>
#include "qmqtt_network_p.h"
#include "qmqtt_socket_p.h"
#include "qmqtt_ssl_socket_p.h"
#include "qmqtt_timer_p.h"
#include "qmqtt_websocket_p.h"

const QString DEFAULT_HOST_NAME = QStringLiteral("localhost");
const QHostAddress DEFAULT_HOST = QHostAddress::LocalHost;
const quint16 DEFAULT_PORT = 1883;
const quint16 DEFAULT_SSL_PORT = 8883;
const bool DEFAULT_AUTORECONNECT = false;
const int DEFAULT_AUTORECONNECT_INTERVAL_MS = 5000;

QMQTT::Network::Network(QObject* parent)
    : NetworkInterface(parent)
    , _port(DEFAULT_PORT)
    , _host(DEFAULT_HOST)
    , _autoReconnect(DEFAULT_AUTORECONNECT)
    , _autoReconnectInterval(DEFAULT_AUTORECONNECT_INTERVAL_MS)
    , _socket(new QMQTT::Socket)
    , _autoReconnectTimer(new QMQTT::Timer)
    , _readState(Header)
{
    initialize();
}

#ifndef QT_NO_SSL
QMQTT::Network::Network(const QSslConfiguration &config, bool ignoreSelfSigned, QObject *parent)
    : NetworkInterface(parent)
    , _port(DEFAULT_SSL_PORT)
    , _hostName(DEFAULT_HOST_NAME)
    , _autoReconnect(DEFAULT_AUTORECONNECT)
    , _autoReconnectInterval(DEFAULT_AUTORECONNECT_INTERVAL_MS)
    , _socket(new QMQTT::SslSocket(config, ignoreSelfSigned))
    , _autoReconnectTimer(new QMQTT::Timer)
    , _readState(Header)
{
    initialize();
}
#endif // QT_NO_SSL

#ifdef QT_WEBSOCKETS_LIB
QMQTT::Network::Network(const QString& origin, QWebSocketProtocol::Version version,
                        bool ignoreSelfSigned, QObject* parent)
    : NetworkInterface(parent)
    , _port(DEFAULT_SSL_PORT)
    , _hostName(DEFAULT_HOST_NAME)
    , _autoReconnect(DEFAULT_AUTORECONNECT)
    , _autoReconnectInterval(DEFAULT_AUTORECONNECT_INTERVAL_MS)
    , _socket(new QMQTT::WebSocket(origin, version, ignoreSelfSigned))
    , _autoReconnectTimer(new QMQTT::Timer)
    , _readState(Header)
{
    initialize();
}
#endif // QT_WEBSOCKETS_LIB

QMQTT::Network::Network(SocketInterface* socketInterface, TimerInterface* timerInterface,
                        QObject* parent)
    : NetworkInterface(parent)
    , _port(DEFAULT_PORT)
    , _host(DEFAULT_HOST)
    , _autoReconnect(DEFAULT_AUTORECONNECT)
    , _autoReconnectInterval(DEFAULT_AUTORECONNECT_INTERVAL_MS)
    , _socket(socketInterface)
    , _autoReconnectTimer(timerInterface)
    , _readState(Header)
{
    initialize();
}

void QMQTT::Network::initialize()
{
    _socket->setParent(this);
    _autoReconnectTimer->setParent(this);
    _autoReconnectTimer->setSingleShot(true);
    _autoReconnectTimer->setInterval(_autoReconnectInterval);

    QObject::connect(_socket, &SocketInterface::connected, this, &Network::connected);
    QObject::connect(_socket, &SocketInterface::disconnected, this, &Network::onDisconnected);
    QObject::connect(_socket->ioDevice(), &QIODevice::readyRead, this, &Network::onSocketReadReady);
    QObject::connect(
        _autoReconnectTimer, &TimerInterface::timeout,
        this, static_cast<void (Network::*)()>(&Network::connectToHost));
    QObject::connect(_socket,
        static_cast<void (SocketInterface::*)(QAbstractSocket::SocketError)>(&SocketInterface::error),
        this, &Network::onSocketError);
}

QMQTT::Network::~Network()
{
}

bool QMQTT::Network::isConnectedToHost() const
{
    return _socket->state() == QAbstractSocket::ConnectedState;
}

void QMQTT::Network::connectToHost(const QHostAddress& host, const quint16 port)
{
    // Reset the hostname, because if it is not empty connectToHost() will use it instead of _host.
    _hostName.clear();
    _host = host;
    _port = port;
    connectToHost();
}

void QMQTT::Network::connectToHost(const QString& hostName, const quint16 port)
{
    _hostName = hostName;
    _port = port;
    connectToHost();
}

void QMQTT::Network::connectToHost()
{
    _readState = Header;
    if (_hostName.isEmpty())
    {
        _socket->connectToHost(_host, _port);
    }
    else
    {
        _socket->connectToHost(_hostName, _port);
    }
}

void QMQTT::Network::onSocketError(QAbstractSocket::SocketError socketError)
{
    emit error(socketError);
    if(_autoReconnect)
    {
        _autoReconnectTimer->start();
    }
}

void QMQTT::Network::sendFrame(Frame& frame)
{
    if(_socket->state() == QAbstractSocket::ConnectedState)
    {
        QDataStream out(_socket->ioDevice());
        frame.write(out);
    }
}

void QMQTT::Network::disconnectFromHost()
{
    _socket->disconnectFromHost();
}

QAbstractSocket::SocketState QMQTT::Network::state() const
{
    return _socket->state();
}

bool QMQTT::Network::autoReconnect() const
{
    return _autoReconnect;
}

void QMQTT::Network::setAutoReconnect(const bool autoReconnect)
{
    _autoReconnect = autoReconnect;
}

int QMQTT::Network::autoReconnectInterval() const
{
    return _autoReconnectInterval;
}

void QMQTT::Network::setAutoReconnectInterval(const int autoReconnectInterval)
{
    _autoReconnectInterval = autoReconnectInterval;
    _autoReconnectTimer->setInterval(_autoReconnectInterval);
}

void QMQTT::Network::onSocketReadReady()
{
    QIODevice *ioDevice = _socket->ioDevice();
    // Only read the available (cached) bytes, so the read will never block.
    QByteArray data = ioDevice->read(ioDevice->bytesAvailable());
    foreach(char byte, data) {
        switch (_readState) {
        case Header:
            _header = static_cast<quint8>(byte);
            _readState = Length;
            _length = 0;
            _shift = 0;
            _data.resize(0); // keep allocated buffer
            break;
        case Length:
            _length |= (byte & 0x7F) << _shift;
            _shift += 7;
            if ((byte & 0x80) != 0)
                break;
            if (_length == 0) {
                _readState = Header;
                Frame frame(_header, _data);
                emit received(frame);
                break;
            }
            _readState = PayLoad;
            _data.reserve(_length);
            break;
        case PayLoad:
            _data.append(byte);
            --_length;
            if (_length > 0)
                break;
            _readState = Header;
            Frame frame(_header, _data);
            emit received(frame);
            break;
        }
    }
}

void QMQTT::Network::onDisconnected()
{
    emit disconnected();
    if(_autoReconnect)
    {
        _autoReconnectTimer->start();
    }
}
<commit_msg>Remove default hostname and IP address from Network class<commit_after>/*
 * qmqtt_network.cpp - qmqtt network
 *
 * Copyright (c) 2013  Ery Lee <ery.lee at gmail dot com>
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 *   * Redistributions of source code must retain the above copyright notice,
 *     this list of conditions and the following disclaimer.
 *   * Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution.
 *   * Neither the name of mqttc nor the names of its contributors may be used
 *     to endorse or promote products derived from this software without
 *     specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 *
 */
#include <QDataStream>
#include "qmqtt_network_p.h"
#include "qmqtt_socket_p.h"
#include "qmqtt_ssl_socket_p.h"
#include "qmqtt_timer_p.h"
#include "qmqtt_websocket_p.h"

const quint16 DEFAULT_PORT = 1883;
const quint16 DEFAULT_SSL_PORT = 8883;
const bool DEFAULT_AUTORECONNECT = false;
const int DEFAULT_AUTORECONNECT_INTERVAL_MS = 5000;

QMQTT::Network::Network(QObject* parent)
    : NetworkInterface(parent)
    , _port(DEFAULT_PORT)
    , _autoReconnect(DEFAULT_AUTORECONNECT)
    , _autoReconnectInterval(DEFAULT_AUTORECONNECT_INTERVAL_MS)
    , _socket(new QMQTT::Socket)
    , _autoReconnectTimer(new QMQTT::Timer)
    , _readState(Header)
{
    initialize();
}

#ifndef QT_NO_SSL
QMQTT::Network::Network(const QSslConfiguration &config, bool ignoreSelfSigned, QObject *parent)
    : NetworkInterface(parent)
    , _port(DEFAULT_SSL_PORT)
    , _autoReconnect(DEFAULT_AUTORECONNECT)
    , _autoReconnectInterval(DEFAULT_AUTORECONNECT_INTERVAL_MS)
    , _socket(new QMQTT::SslSocket(config, ignoreSelfSigned))
    , _autoReconnectTimer(new QMQTT::Timer)
    , _readState(Header)
{
    initialize();
}
#endif // QT_NO_SSL

#ifdef QT_WEBSOCKETS_LIB
QMQTT::Network::Network(const QString& origin, QWebSocketProtocol::Version version,
                        bool ignoreSelfSigned, QObject* parent)
    : NetworkInterface(parent)
    , _port(DEFAULT_SSL_PORT)
    , _autoReconnect(DEFAULT_AUTORECONNECT)
    , _autoReconnectInterval(DEFAULT_AUTORECONNECT_INTERVAL_MS)
    , _socket(new QMQTT::WebSocket(origin, version, ignoreSelfSigned))
    , _autoReconnectTimer(new QMQTT::Timer)
    , _readState(Header)
{
    initialize();
}
#endif // QT_WEBSOCKETS_LIB

QMQTT::Network::Network(SocketInterface* socketInterface, TimerInterface* timerInterface,
                        QObject* parent)
    : NetworkInterface(parent)
    , _port(DEFAULT_PORT)
    , _autoReconnect(DEFAULT_AUTORECONNECT)
    , _autoReconnectInterval(DEFAULT_AUTORECONNECT_INTERVAL_MS)
    , _socket(socketInterface)
    , _autoReconnectTimer(timerInterface)
    , _readState(Header)
{
    initialize();
}

void QMQTT::Network::initialize()
{
    _socket->setParent(this);
    _autoReconnectTimer->setParent(this);
    _autoReconnectTimer->setSingleShot(true);
    _autoReconnectTimer->setInterval(_autoReconnectInterval);

    QObject::connect(_socket, &SocketInterface::connected, this, &Network::connected);
    QObject::connect(_socket, &SocketInterface::disconnected, this, &Network::onDisconnected);
    QObject::connect(_socket->ioDevice(), &QIODevice::readyRead, this, &Network::onSocketReadReady);
    QObject::connect(
        _autoReconnectTimer, &TimerInterface::timeout,
        this, static_cast<void (Network::*)()>(&Network::connectToHost));
    QObject::connect(_socket,
        static_cast<void (SocketInterface::*)(QAbstractSocket::SocketError)>(&SocketInterface::error),
        this, &Network::onSocketError);
}

QMQTT::Network::~Network()
{
}

bool QMQTT::Network::isConnectedToHost() const
{
    return _socket->state() == QAbstractSocket::ConnectedState;
}

void QMQTT::Network::connectToHost(const QHostAddress& host, const quint16 port)
{
    // Reset the hostname, because if it is not empty connectToHost() will use it instead of _host.
    _hostName.clear();
    _host = host;
    _port = port;
    connectToHost();
}

void QMQTT::Network::connectToHost(const QString& hostName, const quint16 port)
{
    _hostName = hostName;
    _port = port;
    connectToHost();
}

void QMQTT::Network::connectToHost()
{
    _readState = Header;
    if (_hostName.isEmpty())
    {
        _socket->connectToHost(_host, _port);
    }
    else
    {
        _socket->connectToHost(_hostName, _port);
    }
}

void QMQTT::Network::onSocketError(QAbstractSocket::SocketError socketError)
{
    emit error(socketError);
    if(_autoReconnect)
    {
        _autoReconnectTimer->start();
    }
}

void QMQTT::Network::sendFrame(Frame& frame)
{
    if(_socket->state() == QAbstractSocket::ConnectedState)
    {
        QDataStream out(_socket->ioDevice());
        frame.write(out);
    }
}

void QMQTT::Network::disconnectFromHost()
{
    _socket->disconnectFromHost();
}

QAbstractSocket::SocketState QMQTT::Network::state() const
{
    return _socket->state();
}

bool QMQTT::Network::autoReconnect() const
{
    return _autoReconnect;
}

void QMQTT::Network::setAutoReconnect(const bool autoReconnect)
{
    _autoReconnect = autoReconnect;
}

int QMQTT::Network::autoReconnectInterval() const
{
    return _autoReconnectInterval;
}

void QMQTT::Network::setAutoReconnectInterval(const int autoReconnectInterval)
{
    _autoReconnectInterval = autoReconnectInterval;
    _autoReconnectTimer->setInterval(_autoReconnectInterval);
}

void QMQTT::Network::onSocketReadReady()
{
    QIODevice *ioDevice = _socket->ioDevice();
    // Only read the available (cached) bytes, so the read will never block.
    QByteArray data = ioDevice->read(ioDevice->bytesAvailable());
    foreach(char byte, data) {
        switch (_readState) {
        case Header:
            _header = static_cast<quint8>(byte);
            _readState = Length;
            _length = 0;
            _shift = 0;
            _data.resize(0); // keep allocated buffer
            break;
        case Length:
            _length |= (byte & 0x7F) << _shift;
            _shift += 7;
            if ((byte & 0x80) != 0)
                break;
            if (_length == 0) {
                _readState = Header;
                Frame frame(_header, _data);
                emit received(frame);
                break;
            }
            _readState = PayLoad;
            _data.reserve(_length);
            break;
        case PayLoad:
            _data.append(byte);
            --_length;
            if (_length > 0)
                break;
            _readState = Header;
            Frame frame(_header, _data);
            emit received(frame);
            break;
        }
    }
}

void QMQTT::Network::onDisconnected()
{
    emit disconnected();
    if(_autoReconnect)
    {
        _autoReconnectTimer->start();
    }
}
<|endoftext|>


<commit_before>/**************************************************************************
**
** This file is part of Qt Creator
**
** Copyright (c) 2012 Nokia Corporation and/or its subsidiary(-ies).
**
** Contact: http://www.qt-project.org/
**
**
** GNU Lesser General Public License Usage
**
** This file may be used under the terms of the GNU Lesser General Public
** License version 2.1 as published by the Free Software Foundation and
** appearing in the file LICENSE.LGPL included in the packaging of this file.
** Please review the following information to ensure the GNU Lesser General
** Public License version 2.1 requirements will be met:
** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** In addition, as a special exception, Nokia gives you certain additional
** rights. These rights are described in the Nokia Qt LGPL Exception
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
** Other Usage
**
** Alternatively, this file may be used in accordance with the terms and
** conditions contained in a signed written agreement between you and Nokia.
**
**
**************************************************************************/

#include "qt4projectconfigwidget.h"

#include "makestep.h"
#include "qmakestep.h"
#include "qt4project.h"
#include "qt4projectmanagerconstants.h"
#include "qt4projectmanager.h"
#include "qt4buildconfiguration.h"
#include "ui_qt4projectconfigwidget.h"

#include <coreplugin/icore.h>
#include <coreplugin/idocument.h>

#include <projectexplorer/target.h>
#include <projectexplorer/toolchain.h>
#include <projectexplorer/task.h>
#include <projectexplorer/projectexplorerconstants.h>
#include <projectexplorer/buildconfiguration.h>
#include <qtsupport/qtversionfactory.h>
#include <qtsupport/baseqtversion.h>
#include <qtsupport/qtkitinformation.h>
#include <qtsupport/qtsupportconstants.h>
#include <qtsupport/qtversionmanager.h>
#include <utils/qtcassert.h>
#include <utils/qtcprocess.h>
#include <extensionsystem/pluginmanager.h>

#include <QFileDialog>
#include <QPushButton>
#include <utils/detailswidget.h>

namespace {
bool debug = false;
}

using namespace Qt4ProjectManager;
using namespace Qt4ProjectManager::Internal;
using namespace ProjectExplorer;

Qt4ProjectConfigWidget::Qt4ProjectConfigWidget(ProjectExplorer::Target *target)
    : BuildConfigWidget(),
      m_buildConfiguration(0),
      m_ignoreChange(false)
{
    QVBoxLayout *vbox = new QVBoxLayout(this);
    vbox->setMargin(0);
    m_detailsContainer = new Utils::DetailsWidget(this);
    m_detailsContainer->setState(Utils::DetailsWidget::NoSummary);
    vbox->addWidget(m_detailsContainer);
    QWidget *details = new QWidget(m_detailsContainer);
    m_detailsContainer->setWidget(details);
    m_ui = new Ui::Qt4ProjectConfigWidget();
    m_ui->setupUi(details);

    m_browseButton = m_ui->shadowBuildDirEdit->buttonAtIndex(0);

    m_ui->shadowBuildDirEdit->setPromptDialogTitle(tr("Shadow Build Directory"));
    m_ui->shadowBuildDirEdit->setExpectedKind(Utils::PathChooser::ExistingDirectory);

    connect(m_ui->shadowBuildCheckBox, SIGNAL(clicked(bool)),
            this, SLOT(shadowBuildClicked(bool)));

    connect(m_ui->shadowBuildDirEdit, SIGNAL(beforeBrowsing()),
            this, SLOT(onBeforeBeforeShadowBuildDirBrowsed()));

    connect(m_ui->shadowBuildDirEdit, SIGNAL(changed(QString)),
            this, SLOT(shadowBuildEdited()));

    Qt4Project *project = static_cast<Qt4Project *>(target->project());
    connect(project, SIGNAL(environmentChanged()), this, SLOT(environmentChanged()));
    connect(project, SIGNAL(buildDirectoryInitialized()), this, SLOT(updateProblemLabel()));
}

Qt4ProjectConfigWidget::~Qt4ProjectConfigWidget()
{
    delete m_ui;
}

void Qt4ProjectConfigWidget::updateDetails()
{
    m_detailsContainer->setSummaryText(
                tr("building in %1")
                .arg(QDir::toNativeSeparators(m_buildConfiguration->buildDirectory())));
}

void Qt4ProjectConfigWidget::environmentChanged()
{
    m_ui->shadowBuildDirEdit->setEnvironment(m_buildConfiguration->environment());
}

QString Qt4ProjectConfigWidget::displayName() const
{
    return tr("General");
}

void Qt4ProjectConfigWidget::init(ProjectExplorer::BuildConfiguration *bc)
{
    QTC_ASSERT(bc, return);

    if (debug)
        qDebug() << "Qt4ProjectConfigWidget::init() for" << bc->displayName();

    if (m_buildConfiguration) {
        disconnect(m_buildConfiguration, SIGNAL(buildDirectoryChanged()),
                this, SLOT(buildDirectoryChanged()));
        disconnect(m_buildConfiguration, SIGNAL(qmakeBuildConfigurationChanged()),
                   this, SLOT(updateProblemLabel()));
    }
    m_buildConfiguration = static_cast<Qt4BuildConfiguration *>(bc);
    m_ui->shadowBuildDirEdit->setEnvironment(m_buildConfiguration->environment());

    connect(m_buildConfiguration, SIGNAL(buildDirectoryChanged()),
            this, SLOT(buildDirectoryChanged()));
    connect(m_buildConfiguration, SIGNAL(qmakeBuildConfigurationChanged()),
            this, SLOT(updateProblemLabel()));

    m_ui->shadowBuildDirEdit->setBaseDirectory(m_buildConfiguration->target()->project()->projectDirectory());

    buildDirectoryChanged();
}

void Qt4ProjectConfigWidget::buildDirectoryChanged()
{
    if (m_ignoreChange)
        return;
    m_ui->shadowBuildDirEdit->setPath(m_buildConfiguration->shadowBuildDirectory());
    bool shadowBuild = m_buildConfiguration->shadowBuild();
    m_ui->shadowBuildCheckBox->setChecked(shadowBuild);
    m_ui->shadowBuildDirEdit->setEnabled(shadowBuild);
    m_browseButton->setEnabled(shadowBuild);

    updateDetails();
    updateProblemLabel();
}

void Qt4ProjectConfigWidget::onBeforeBeforeShadowBuildDirBrowsed()
{
    QString initialDirectory = m_buildConfiguration->target()->project()->projectDirectory();
    if (!initialDirectory.isEmpty())
        m_ui->shadowBuildDirEdit->setInitialBrowsePathBackup(initialDirectory);
}

void Qt4ProjectConfigWidget::shadowBuildClicked(bool checked)
{
    m_ui->shadowBuildDirEdit->setEnabled(checked);
    m_browseButton->setEnabled(checked);
    bool b = m_ui->shadowBuildCheckBox->isChecked();

    m_ignoreChange = true;
    m_buildConfiguration->setShadowBuildAndDirectory(b, m_ui->shadowBuildDirEdit->rawPath());
    m_ignoreChange = false;

    updateDetails();
    updateProblemLabel();
}

void Qt4ProjectConfigWidget::shadowBuildEdited()
{
    if (m_buildConfiguration->shadowBuildDirectory() == m_ui->shadowBuildDirEdit->rawPath())
        return;
    m_ignoreChange = true;
    m_buildConfiguration->setShadowBuildAndDirectory(m_buildConfiguration->shadowBuild(), m_ui->shadowBuildDirEdit->rawPath());
    m_ignoreChange = false;

    // if the directory already exists
    // check if we have a build in there and
    // offer to import it
    updateProblemLabel();
    updateDetails();
}

void Qt4ProjectConfigWidget::updateProblemLabel()
{
    bool targetMismatch = false;
    bool incompatibleBuild = false;
    bool allGood = false;

    ProjectExplorer::Kit *k = m_buildConfiguration->target()->kit();
    const QString proFileName = m_buildConfiguration->target()->project()->document()->fileName();

    QtSupport::BaseQtVersion *version = QtSupport::QtKitInformation::qtVersion(k);
    if (!version) {
        m_ui->problemLabel->setVisible(true);
        m_ui->warningLabel->setVisible(true);
        m_ui->problemLabel->setText(tr("This target cannot build this project since it does not define a "
                                       "Qt version."));
        return;
    }

    // we only show if we actually have a qmake and makestep
    if (m_buildConfiguration->qmakeStep() && m_buildConfiguration->makeStep()) {
        QString makefile = m_buildConfiguration->buildDirectory() + QLatin1Char('/');
        if (m_buildConfiguration->makefile().isEmpty())
            makefile.append(QLatin1String("Makefile"));
        else
            makefile.append(m_buildConfiguration->makefile());

        switch (m_buildConfiguration->compareToImportFrom(makefile)) {
        case Qt4BuildConfiguration::MakefileMatches:
            allGood = true;
            break;
        case Qt4BuildConfiguration::MakefileMissing:
            allGood = true;
            break;
        case Qt4BuildConfiguration::MakefileIncompatible:
            incompatibleBuild = true;
            break;
        case Qt4BuildConfiguration::MakefileForWrongProject:
            targetMismatch = true;
            break;
        }
    }

    QString shadowBuildWarning;
    if (!version->supportsShadowBuilds() && m_buildConfiguration->shadowBuild()) {
        shadowBuildWarning =tr("The Qt version %1 does not support shadow builds, building might fail.")
                .arg(version->displayName())
                + QLatin1String("
");
    }

    if (allGood) {
        QString buildDirectory = m_buildConfiguration->target()->project()->projectDirectory();;
        if (m_buildConfiguration->shadowBuild())
            buildDirectory = m_buildConfiguration->buildDirectory();
        QList<ProjectExplorer::Task> issues;
        issues = version->reportIssues(proFileName, buildDirectory);
        qSort(issues);

        if (issues.isEmpty() && shadowBuildWarning.isEmpty()) {
            m_ui->problemLabel->setVisible(false);
            m_ui->warningLabel->setVisible(false);
        } else {
            m_ui->problemLabel->setVisible(true);
            m_ui->warningLabel->setVisible(true);
            QString text = QLatin1String("<nobr>") + shadowBuildWarning;
            foreach (const ProjectExplorer::Task &task, issues) {
                QString type;
                switch (task.type) {
                case ProjectExplorer::Task::Error:
                    type = tr("Error:");
                    type += QLatin1Char(' ');
                    break;
                case ProjectExplorer::Task::Warning:
                    type = tr("Warning:");
                    type += QLatin1Char(' ');
                    break;
                case ProjectExplorer::Task::Unknown:
                default:
                    break;
                }
                if (!text.endsWith(QLatin1String("br>")))
                    text.append(QLatin1String("
"));
                text.append(type + task.description);
            }
            m_ui->problemLabel->setText(text);
        }
    } else if (targetMismatch) {
        m_ui->problemLabel->setVisible(true);
        m_ui->warningLabel->setVisible(true);
        m_ui->problemLabel->setText(shadowBuildWarning + tr("A build for a different project exists in %1, which will be overwritten.",
                                                            "%1 build directory")
                                    .arg(m_ui->shadowBuildDirEdit->path()));
    } else if (incompatibleBuild) {
        m_ui->warningLabel->setVisible(true);
        m_ui->problemLabel->setVisible(true);
        m_ui->problemLabel->setText(shadowBuildWarning +tr("An incompatible build exists in %1, which will be overwritten.",
                                                           "%1 build directory")
                                    .arg(m_ui->shadowBuildDirEdit->path()));
    } else if (!shadowBuildWarning.isEmpty()) {
        m_ui->warningLabel->setVisible(true);
        m_ui->problemLabel->setVisible(true);
        m_ui->problemLabel->setText(shadowBuildWarning);
    }
}
<commit_msg>Qt4Project: Fix updating of problem label on kit changes<commit_after>/**************************************************************************
**
** This file is part of Qt Creator
**
** Copyright (c) 2012 Nokia Corporation and/or its subsidiary(-ies).
**
** Contact: http://www.qt-project.org/
**
**
** GNU Lesser General Public License Usage
**
** This file may be used under the terms of the GNU Lesser General Public
** License version 2.1 as published by the Free Software Foundation and
** appearing in the file LICENSE.LGPL included in the packaging of this file.
** Please review the following information to ensure the GNU Lesser General
** Public License version 2.1 requirements will be met:
** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** In addition, as a special exception, Nokia gives you certain additional
** rights. These rights are described in the Nokia Qt LGPL Exception
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
** Other Usage
**
** Alternatively, this file may be used in accordance with the terms and
** conditions contained in a signed written agreement between you and Nokia.
**
**
**************************************************************************/

#include "qt4projectconfigwidget.h"

#include "makestep.h"
#include "qmakestep.h"
#include "qt4project.h"
#include "qt4projectmanagerconstants.h"
#include "qt4projectmanager.h"
#include "qt4buildconfiguration.h"
#include "ui_qt4projectconfigwidget.h"

#include <coreplugin/icore.h>
#include <coreplugin/idocument.h>

#include <projectexplorer/target.h>
#include <projectexplorer/toolchain.h>
#include <projectexplorer/task.h>
#include <projectexplorer/projectexplorerconstants.h>
#include <projectexplorer/buildconfiguration.h>
#include <qtsupport/qtversionfactory.h>
#include <qtsupport/baseqtversion.h>
#include <qtsupport/qtkitinformation.h>
#include <qtsupport/qtsupportconstants.h>
#include <qtsupport/qtversionmanager.h>
#include <utils/qtcassert.h>
#include <utils/qtcprocess.h>
#include <extensionsystem/pluginmanager.h>

#include <QFileDialog>
#include <QPushButton>
#include <utils/detailswidget.h>

namespace {
bool debug = false;
}

using namespace Qt4ProjectManager;
using namespace Qt4ProjectManager::Internal;
using namespace ProjectExplorer;

Qt4ProjectConfigWidget::Qt4ProjectConfigWidget(ProjectExplorer::Target *target)
    : BuildConfigWidget(),
      m_buildConfiguration(0),
      m_ignoreChange(false)
{
    QVBoxLayout *vbox = new QVBoxLayout(this);
    vbox->setMargin(0);
    m_detailsContainer = new Utils::DetailsWidget(this);
    m_detailsContainer->setState(Utils::DetailsWidget::NoSummary);
    vbox->addWidget(m_detailsContainer);
    QWidget *details = new QWidget(m_detailsContainer);
    m_detailsContainer->setWidget(details);
    m_ui = new Ui::Qt4ProjectConfigWidget();
    m_ui->setupUi(details);

    m_browseButton = m_ui->shadowBuildDirEdit->buttonAtIndex(0);

    m_ui->shadowBuildDirEdit->setPromptDialogTitle(tr("Shadow Build Directory"));
    m_ui->shadowBuildDirEdit->setExpectedKind(Utils::PathChooser::ExistingDirectory);

    connect(m_ui->shadowBuildCheckBox, SIGNAL(clicked(bool)),
            this, SLOT(shadowBuildClicked(bool)));

    connect(m_ui->shadowBuildDirEdit, SIGNAL(beforeBrowsing()),
            this, SLOT(onBeforeBeforeShadowBuildDirBrowsed()));

    connect(m_ui->shadowBuildDirEdit, SIGNAL(changed(QString)),
            this, SLOT(shadowBuildEdited()));

    Qt4Project *project = static_cast<Qt4Project *>(target->project());
    connect(project, SIGNAL(environmentChanged()), this, SLOT(environmentChanged()));
    connect(project, SIGNAL(buildDirectoryInitialized()), this, SLOT(updateProblemLabel()));

    connect(target, SIGNAL(kitChanged()), this, SLOT(updateProblemLabel()));
}

Qt4ProjectConfigWidget::~Qt4ProjectConfigWidget()
{
    delete m_ui;
}

void Qt4ProjectConfigWidget::updateDetails()
{
    m_detailsContainer->setSummaryText(
                tr("building in %1")
                .arg(QDir::toNativeSeparators(m_buildConfiguration->buildDirectory())));
}

void Qt4ProjectConfigWidget::environmentChanged()
{
    m_ui->shadowBuildDirEdit->setEnvironment(m_buildConfiguration->environment());
}

QString Qt4ProjectConfigWidget::displayName() const
{
    return tr("General");
}

void Qt4ProjectConfigWidget::init(ProjectExplorer::BuildConfiguration *bc)
{
    QTC_ASSERT(bc, return);

    if (debug)
        qDebug() << "Qt4ProjectConfigWidget::init() for" << bc->displayName();

    if (m_buildConfiguration) {
        disconnect(m_buildConfiguration, SIGNAL(buildDirectoryChanged()),
                this, SLOT(buildDirectoryChanged()));
        disconnect(m_buildConfiguration, SIGNAL(qmakeBuildConfigurationChanged()),
                   this, SLOT(updateProblemLabel()));
    }
    m_buildConfiguration = static_cast<Qt4BuildConfiguration *>(bc);
    m_ui->shadowBuildDirEdit->setEnvironment(m_buildConfiguration->environment());

    connect(m_buildConfiguration, SIGNAL(buildDirectoryChanged()),
            this, SLOT(buildDirectoryChanged()));
    connect(m_buildConfiguration, SIGNAL(qmakeBuildConfigurationChanged()),
            this, SLOT(updateProblemLabel()));

    m_ui->shadowBuildDirEdit->setBaseDirectory(m_buildConfiguration->target()->project()->projectDirectory());

    buildDirectoryChanged();
}

void Qt4ProjectConfigWidget::buildDirectoryChanged()
{
    if (m_ignoreChange)
        return;
    m_ui->shadowBuildDirEdit->setPath(m_buildConfiguration->shadowBuildDirectory());
    bool shadowBuild = m_buildConfiguration->shadowBuild();
    m_ui->shadowBuildCheckBox->setChecked(shadowBuild);
    m_ui->shadowBuildDirEdit->setEnabled(shadowBuild);
    m_browseButton->setEnabled(shadowBuild);

    updateDetails();
    updateProblemLabel();
}

void Qt4ProjectConfigWidget::onBeforeBeforeShadowBuildDirBrowsed()
{
    QString initialDirectory = m_buildConfiguration->target()->project()->projectDirectory();
    if (!initialDirectory.isEmpty())
        m_ui->shadowBuildDirEdit->setInitialBrowsePathBackup(initialDirectory);
}

void Qt4ProjectConfigWidget::shadowBuildClicked(bool checked)
{
    m_ui->shadowBuildDirEdit->setEnabled(checked);
    m_browseButton->setEnabled(checked);
    bool b = m_ui->shadowBuildCheckBox->isChecked();

    m_ignoreChange = true;
    m_buildConfiguration->setShadowBuildAndDirectory(b, m_ui->shadowBuildDirEdit->rawPath());
    m_ignoreChange = false;

    updateDetails();
    updateProblemLabel();
}

void Qt4ProjectConfigWidget::shadowBuildEdited()
{
    if (m_buildConfiguration->shadowBuildDirectory() == m_ui->shadowBuildDirEdit->rawPath())
        return;
    m_ignoreChange = true;
    m_buildConfiguration->setShadowBuildAndDirectory(m_buildConfiguration->shadowBuild(), m_ui->shadowBuildDirEdit->rawPath());
    m_ignoreChange = false;

    // if the directory already exists
    // check if we have a build in there and
    // offer to import it
    updateProblemLabel();
    updateDetails();
}

void Qt4ProjectConfigWidget::updateProblemLabel()
{
    bool targetMismatch = false;
    bool incompatibleBuild = false;
    bool allGood = false;

    ProjectExplorer::Kit *k = m_buildConfiguration->target()->kit();
    const QString proFileName = m_buildConfiguration->target()->project()->document()->fileName();

    QtSupport::BaseQtVersion *version = QtSupport::QtKitInformation::qtVersion(k);
    if (!version) {
        m_ui->problemLabel->setVisible(true);
        m_ui->warningLabel->setVisible(true);
        m_ui->problemLabel->setText(tr("This target cannot build this project since it does not define a "
                                       "Qt version."));
        return;
    }

    // we only show if we actually have a qmake and makestep
    if (m_buildConfiguration->qmakeStep() && m_buildConfiguration->makeStep()) {
        QString makefile = m_buildConfiguration->buildDirectory() + QLatin1Char('/');
        if (m_buildConfiguration->makefile().isEmpty())
            makefile.append(QLatin1String("Makefile"));
        else
            makefile.append(m_buildConfiguration->makefile());

        switch (m_buildConfiguration->compareToImportFrom(makefile)) {
        case Qt4BuildConfiguration::MakefileMatches:
            allGood = true;
            break;
        case Qt4BuildConfiguration::MakefileMissing:
            allGood = true;
            break;
        case Qt4BuildConfiguration::MakefileIncompatible:
            incompatibleBuild = true;
            break;
        case Qt4BuildConfiguration::MakefileForWrongProject:
            targetMismatch = true;
            break;
        }
    }

    QString shadowBuildWarning;
    if (!version->supportsShadowBuilds() && m_buildConfiguration->shadowBuild()) {
        shadowBuildWarning =tr("The Qt version %1 does not support shadow builds, building might fail.")
                .arg(version->displayName())
                + QLatin1String("
");
    }

    if (allGood) {
        QString buildDirectory = m_buildConfiguration->target()->project()->projectDirectory();;
        if (m_buildConfiguration->shadowBuild())
            buildDirectory = m_buildConfiguration->buildDirectory();
        QList<ProjectExplorer::Task> issues;
        issues = version->reportIssues(proFileName, buildDirectory);
        qSort(issues);

        if (issues.isEmpty() && shadowBuildWarning.isEmpty()) {
            m_ui->problemLabel->setVisible(false);
            m_ui->warningLabel->setVisible(false);
        } else {
            m_ui->problemLabel->setVisible(true);
            m_ui->warningLabel->setVisible(true);
            QString text = QLatin1String("<nobr>") + shadowBuildWarning;
            foreach (const ProjectExplorer::Task &task, issues) {
                QString type;
                switch (task.type) {
                case ProjectExplorer::Task::Error:
                    type = tr("Error:");
                    type += QLatin1Char(' ');
                    break;
                case ProjectExplorer::Task::Warning:
                    type = tr("Warning:");
                    type += QLatin1Char(' ');
                    break;
                case ProjectExplorer::Task::Unknown:
                default:
                    break;
                }
                if (!text.endsWith(QLatin1String("br>")))
                    text.append(QLatin1String("
"));
                text.append(type + task.description);
            }
            m_ui->problemLabel->setText(text);
        }
    } else if (targetMismatch) {
        m_ui->problemLabel->setVisible(true);
        m_ui->warningLabel->setVisible(true);
        m_ui->problemLabel->setText(shadowBuildWarning + tr("A build for a different project exists in %1, which will be overwritten.",
                                                            "%1 build directory")
                                    .arg(m_ui->shadowBuildDirEdit->path()));
    } else if (incompatibleBuild) {
        m_ui->warningLabel->setVisible(true);
        m_ui->problemLabel->setVisible(true);
        m_ui->problemLabel->setText(shadowBuildWarning +tr("An incompatible build exists in %1, which will be overwritten.",
                                                           "%1 build directory")
                                    .arg(m_ui->shadowBuildDirEdit->path()));
    } else if (!shadowBuildWarning.isEmpty()) {
        m_ui->warningLabel->setVisible(true);
        m_ui->problemLabel->setVisible(true);
        m_ui->problemLabel->setText(shadowBuildWarning);
    }
}
<|endoftext|>


<commit_before>#include <tuple>
#include <array>

#include "acmacs-base/color-modifier.hh"
#include "acmacs-base/color-hsv.hh"

// ----------------------------------------------------------------------

using en = std::tuple<Color, std::string_view, Color>;
using namespace std::string_view_literals;

static const std::array test_data{
    en{GREEN, ":s-0.5", 0x7fff7f},
    en{GREEN, ":s-0.9", 0xE5FFE5},
    en{TRANSPARENT, ":s-0.5", TRANSPARENT},
    en{GREEN, ":s0.5", GREEN},
    en{GREEN, ":s+0.5", GREEN},

    en{BLUE, ":b-0.5", 0x00007F},
    en{BLUE, ":b-0.9", 0x000019},
    en{TRANSPARENT, ":b-0.5", TRANSPARENT},
    en{BLUE, ":b0.5", BLUE},
    en{BLUE, ":b+0.5", BLUE},

    en{0xAB3838, ":s-0.5", 0xAB7171},
    en{0xAB3838, ":s-0.9", 0xAB9F9F},
    en{0xAB3838, ":s+0.5", 0xAB1B1B},
    en{0xAB3838, ":s0.9",  0xAB0505},
    en{0x80AB3838, ":s0.9",  0x80AB0505},

    en{0xAB3838, ":b-0.5", 0x551B1B},
    en{0xAB3838, ":b-0.9", 0x110505},
    en{0xAB3838, ":b+0.5", 0xD44545},
    en{0x77AB3838, ":b+0.5", 0x77D44545},
    en{0xAB3838, ":b0.9",  0xF65050},

    en{0xAB3838, ":t-0.1",  0xAB3838},
    en{0xAB3838, ":t0.1",  0x19AB3838},

    en{TRANSPARENT, ":s+0.5", TRANSPARENT},
    en{TRANSPARENT, ":b+0.5", 0xFF7F7F7F},
    en{TRANSPARENT, ":t+0.5", TRANSPARENT},
    en{TRANSPARENT, ":t-0.5", 0x7F000000},
    en{0xFF808080,  ":t-0.5", 0x7F808080},

    en{BLACK,  ":s-0.5", BLACK},
    en{BLACK,  ":b-0.5", BLACK},
    en{BLACK,  ":t+0.5", 0x7F000000},

    en{BLUE, ":p+0.5", 0x7F7FFF},
    en{BLACK, ":p+0.5", 0x7F7F7F},
    en{0x7F7F7F, ":p+0.5", 0xBFBFBF},
};

int main()
{
    int errors{0};
    for (const auto& [source, modifier, expected] : test_data) {
        Color color{source};
        if (acmacs::color::modify(color, modifier) != expected) {
            ++errors;
            AD_ERROR("color modifier failed: \"{}\"({}) + \"{}\" -> \"{}\"({})  expected: \"{}\"({})", source, acmacs::color::HSV{source}, modifier, color, acmacs::color::HSV{color}, expected,
                     acmacs::color::HSV{expected});
        }
    }
    return errors;
}

// ----------------------------------------------------------------------
/// Local Variables:
/// eval: (if (fboundp 'eu-rename-buffer) (eu-rename-buffer))
/// End:
<commit_msg>debugging color modifier<commit_after>#include <tuple>
#include <array>

#include "acmacs-base/color-modifier.hh"
#include "acmacs-base/color-hsv.hh"

// ----------------------------------------------------------------------

using en = std::tuple<Color, std::string_view, Color>;
using namespace std::string_view_literals;

static const std::array test_data{
    en{GREEN, ":s-0.5", 0x7fff7f},
    en{GREEN, ":s-0.9", 0xE5FFE5},
    en{TRANSPARENT, ":s-0.5", TRANSPARENT},
    en{GREEN, ":s0.5", GREEN},
    en{GREEN, ":s+0.5", GREEN},
    en{0x6495ED, ":s=0.5", 0x78aaf1},

    en{BLUE, ":b-0.5", 0x00007F},
    en{BLUE, ":b-0.9", 0x000019},
    en{TRANSPARENT, ":b-0.5", TRANSPARENT},
    en{BLUE, ":b0.5", BLUE},
    en{BLUE, ":b+0.5", BLUE},

    en{0xAB3838, ":s-0.5", 0xAB7171},
    en{0xAB3838, ":s-0.9", 0xAB9F9F},
    en{0xAB3838, ":s+0.5", 0xAB1B1B},
    en{0xAB3838, ":s0.9",  0xAB0505},
    en{0x80AB3838, ":s0.9",  0x80AB0505},

    en{0xAB3838, ":b-0.5", 0x551B1B},
    en{0xAB3838, ":b-0.9", 0x110505},
    en{0xAB3838, ":b+0.5", 0xD44545},
    en{0x77AB3838, ":b+0.5", 0x77D44545},
    en{0xAB3838, ":b0.9",  0xF65050},

    en{0xAB3838, ":t-0.1",  0xAB3838},
    en{0xAB3838, ":t0.1",  0x19AB3838},

    en{TRANSPARENT, ":s+0.5", TRANSPARENT},
    en{TRANSPARENT, ":b+0.5", 0xFF7F7F7F},
    en{TRANSPARENT, ":t+0.5", TRANSPARENT},
    en{TRANSPARENT, ":t-0.5", 0x7F000000},
    en{0xFF808080,  ":t-0.5", 0x7F808080},

    en{BLACK,  ":s-0.5", BLACK},
    en{BLACK,  ":b-0.5", BLACK},
    en{BLACK,  ":t+0.5", 0x7F000000},

    en{BLUE, ":p+0.5", 0x7F7FFF},
    en{BLACK, ":p+0.5", 0x7F7F7F},
    en{0x7F7F7F, ":p+0.5", 0xBFBFBF},
};

int main()
{
    int errors{0};
    for (const auto& [source, modifier, expected] : test_data) {
        Color color{source};
        if (acmacs::color::modify(color, modifier) != expected) {
            ++errors;
            AD_ERROR("color modifier failed: \"{}\"({}) + \"{}\" -> \"{}\"({})  expected: \"{}\"({})", source, acmacs::color::HSV{source}, modifier, color, acmacs::color::HSV{color}, expected,
                     acmacs::color::HSV{expected});
        }
    }
    return errors;
}

// ----------------------------------------------------------------------
/// Local Variables:
/// eval: (if (fboundp 'eu-rename-buffer) (eu-rename-buffer))
/// End:
<|endoftext|>