<commit_before>/****************************************************************************
**
** Copyright (C) 2012 Digia Plc and/or its subsidiary(-ies).
** Contact: http://www.qt-project.org/legal
**
** This file is part of Qt Creator.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and Digia.  For licensing terms and
** conditions see http://qt.digia.com/licensing.  For further information
** use the contact form at http://qt.digia.com/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, 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, Digia gives you certain additional
** rights.  These rights are described in the Digia Qt LGPL Exception
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
****************************************************************************/

#include "qmljs_engine.h"
#include "qv4object.h"
#include "qv4objectproto.h"
#include "qv4mm.h"
#include "PageAllocation.h"
#include "StdLibExtras.h"

#include <QTime>
#include <QVector>
#include <QVector>
#include <QMap>

#include <iostream>
#include <cstdlib>
#include <alloca.h>

using namespace QQmlJS::VM;
using namespace WTF;

static const std::size_t CHUNK_SIZE = 65536;

struct MemoryManager::Data
{
    bool enableGC;
    bool gcBlocked;
    bool scribble;
    bool aggressiveGC;
    ExecutionEngine *engine;

    enum { MaxItemSize = 256 };
    Managed *smallItems[MaxItemSize/16];
    uint nChunks[MaxItemSize/16];
    struct Chunk {
        PageAllocation memory;
        int chunkSize;
    };

    QVector<Chunk> heapChunks;
    QHash<Managed *, uint> protectedObject;

    // statistics:
#ifdef DETAILED_MM_STATS
    QVector<unsigned> allocSizeCounters;
#endif // DETAILED_MM_STATS

    Data(bool enableGC)
        : enableGC(enableGC)
        , gcBlocked(false)
        , engine(0)
    {
        memset(smallItems, 0, sizeof(smallItems));
        memset(nChunks, 0, sizeof(nChunks));
        scribble = !qgetenv("MM_SCRIBBLE").isEmpty();
        aggressiveGC = !qgetenv("MM_AGGRESSIVE_GC").isEmpty();
    }

    ~Data()
    {
        for (QVector<Chunk>::iterator i = heapChunks.begin(), ei = heapChunks.end(); i != ei; ++i)
            i->memory.deallocate();
    }
};

namespace QQmlJS { namespace VM {

bool operator<(const MemoryManager::Data::Chunk &a, const MemoryManager::Data::Chunk &b)
{
    return a.memory.base() < b.memory.base();
}

} } // namespace QQmlJS::VM

MemoryManager::MemoryManager()
    : m_d(new Data(true))
{
    setEnableGC(true);
}

Managed *MemoryManager::alloc(std::size_t size)
{
    if (m_d->aggressiveGC)
        runGC();
#ifdef DETAILED_MM_STATS
    willAllocate(size);
#endif // DETAILED_MM_STATS

    assert(size >= 16);
    assert(size % 16 == 0);

    size_t pos = size >> 4;

    // fits into a small bucket
    assert(size < MemoryManager::Data::MaxItemSize);

    Managed *m = m_d->smallItems[pos];
    if (m)
        goto found;

    // try to free up space, otherwise allocate
    if (!m_d->aggressiveGC) {
        runGC();
        m = m_d->smallItems[pos];
        if (m)
            goto found;
    }

    // no free item available, allocate a new chunk
    {
        // allocate larger chunks at a time to avoid excessive GC, but cap at 64M chunks
        uint shift = ++m_d->nChunks[pos];
        if (shift > 10)
            shift = 10;
        std::size_t allocSize = std::max(size, CHUNK_SIZE*(1 << shift));
        allocSize = roundUpToMultipleOf(WTF::pageSize(), allocSize);
        Data::Chunk allocation;
        allocation.memory = PageAllocation::allocate(allocSize, OSAllocator::JSGCHeapPages);
        allocation.chunkSize = size;
        m_d->heapChunks.append(allocation);
        qSort(m_d->heapChunks);
        char *chunk = (char *)allocation.memory.base();
        char *end = chunk + allocation.memory.size() - size;
        memset(chunk, 0, allocation.memory.size());
        Managed **last = &m_d->smallItems[pos];
        while (chunk <= end) {
            Managed *o = reinterpret_cast<Managed *>(chunk);
            o->inUse = 0;
            o->markBit = 0;
            *last = o;
            last = &o->nextFree;
            chunk += size;
        }
        *last = 0;
        m = m_d->smallItems[pos];
    }

  found:
    m_d->smallItems[pos] = m->nextFree;
    return m;
}

#define SCRIBBLE(obj, c, size) \
    if (m_d->scribble) \
        ::memset((void *)(obj + 1), c, size - sizeof(Managed));

void MemoryManager::mark()
{
    m_d->engine->markObjects();

    for (ExecutionContext *ctxt = engine()->current; ctxt; ctxt = ctxt->parent)
        ctxt->mark();

    for (QHash<Managed *, uint>::const_iterator it = m_d->protectedObject.begin(); it != m_d->protectedObject.constEnd(); ++it)
        it.key()->mark();

    collectFromStack();

    return;
}

std::size_t MemoryManager::sweep()
{
    std::size_t freedCount = 0;

    for (QVector<Data::Chunk>::iterator i = m_d->heapChunks.begin(), ei = m_d->heapChunks.end(); i != ei; ++i)
        freedCount += sweep(reinterpret_cast<char*>(i->memory.base()), i->memory.size(), i->chunkSize);

    return freedCount;
}

std::size_t MemoryManager::sweep(char *chunkStart, std::size_t chunkSize, size_t size)
{
//    qDebug("chunkStart @ %p, size=%x, pos=%x (%x)", chunkStart, size, size>>4, m_d->smallItems[size >> 4]);
    std::size_t freedCount = 0;

    Managed **f = &m_d->smallItems[size >> 4];

    for (char *chunk = chunkStart, *chunkEnd = chunk + chunkSize - size; chunk <= chunkEnd; ) {
        Managed *m = reinterpret_cast<Managed *>(chunk);
//        qDebug("chunk @ %p, size = %lu, in use: %s, mark bit: %s",
//               chunk, m->size, (m->inUse ? "yes" : "no"), (m->markBit ? "true" : "false"));

        assert((intptr_t) chunk % 16 == 0);

        chunk = chunk + size;
        if (m->inUse) {
            if (m->markBit) {
                m->markBit = 0;
            } else {
//                qDebug() << "-- collecting it." << m << *f << &m->nextFree;
                m->~Managed();

                m->nextFree = *f;
                f = &m->nextFree;
                SCRIBBLE(m, 0x99, size);
                ++freedCount;
            }
        }
    }

    return freedCount;
}

bool MemoryManager::isGCBlocked() const
{
    return m_d->gcBlocked;
}

void MemoryManager::setGCBlocked(bool blockGC)
{
    m_d->gcBlocked = blockGC;
}

void MemoryManager::runGC()
{
    if (!m_d->enableGC || m_d->gcBlocked) {
//        qDebug() << "Not running GC.";
        return;
    }

//    QTime t; t.start();

//    qDebug() << ">>>>>>>>runGC";

    mark();
//    std::cerr << "GC: marked " << marks
//              << " objects in " << t.elapsed()
//              << "ms" << std::endl;

//    t.restart();
    /*std::size_t freedCount =*/ sweep();
//    std::cerr << "GC: sweep freed " << freedCount
//              << " objects in " << t.elapsed()
//              << "ms" << std::endl;
}

void MemoryManager::setEnableGC(bool enableGC)
{
    m_d->enableGC = enableGC;
}

MemoryManager::~MemoryManager()
{
    sweep();
}

void MemoryManager::protect(Managed *m)
{
    ++m_d->protectedObject[m];
}

void MemoryManager::unprotect(Managed *m)
{
    if (!--m_d->protectedObject[m])
        m_d->protectedObject.remove(m);
}

static inline void add(QVector<Managed *> &values, const Value &v)
{
    if (Object *o = v.asObject())
        values.append(o);
}

void MemoryManager::setExecutionEngine(ExecutionEngine *engine)
{
    m_d->engine = engine;
}

void MemoryManager::dumpStats() const
{
#ifdef DETAILED_MM_STATS
    std::cerr << "=================" << std::endl;
    std::cerr << "Allocation stats:" << std::endl;
    std::cerr << "Requests for each chunk size:" << std::endl;
    for (int i = 0; i < m_d->allocSizeCounters.size(); ++i) {
        if (unsigned count = m_d->allocSizeCounters[i]) {
            std::cerr << "\t" << (i << 4) << " bytes chunks: " << count << std::endl;
        }
    }
#endif // DETAILED_MM_STATS
}

ExecutionEngine *MemoryManager::engine() const
{
    return m_d->engine;
}

#ifdef DETAILED_MM_STATS
void MemoryManager::willAllocate(std::size_t size)
{
    unsigned alignedSize = (size + 15) >> 4;
    QVector<unsigned> &counters = m_d->allocSizeCounters;
    if ((unsigned) counters.size() < alignedSize + 1)
        counters.resize(alignedSize + 1);
    counters[alignedSize]++;
}

#endif // DETAILED_MM_STATS

void MemoryManager::collectFromStack() const
{
    if (!m_d->heapChunks.count())
        return;

    quintptr valueOnStack = 0;

#if USE(PTHREADS)
#if OS(DARWIN)
    void* stackTop = 0;
    stackTop = pthread_get_stackaddr_np(pthread_self());
    quintptr *top = static_cast<quintptr *>(stackTop);
#else
    void* stackBottom = 0;
    pthread_attr_t attr;
    pthread_getattr_np(pthread_self(), &attr);
    size_t stackSize = 0;
    pthread_attr_getstack(&attr, &stackBottom, &stackSize);
    pthread_attr_destroy(&attr);

    quintptr *top = static_cast<quintptr *>(stackBottom) + stackSize/sizeof(quintptr);
#endif
#endif
//    qDebug() << "stack:" << hex << stackTop << stackSize << (stackTop + stackSize);

    quintptr *current = (&valueOnStack) + 1;
//    qDebug() << "collectFromStack" << top << current << &valueOnStack;

    char** heapChunkBoundaries = (char**)alloca(m_d->heapChunks.count() * 2 * sizeof(char*));
    char** heapChunkBoundariesEnd = heapChunkBoundaries + 2 * m_d->heapChunks.count();
    int i = 0;
    for (QVector<Data::Chunk>::Iterator it = m_d->heapChunks.begin(), end =
         m_d->heapChunks.end(); it != end; ++it) {
        heapChunkBoundaries[i++] = reinterpret_cast<char*>(it->memory.base());
        heapChunkBoundaries[i++] = reinterpret_cast<char*>(it->memory.base()) + it->memory.size() - it->chunkSize;
    }

    for (; current < top; ++current) {
        char* genericPtr =
#if CPU(X86_64)
                reinterpret_cast<char *>((*current) & ~(quint64(Value::Type_Mask) << Value::Tag_Shift));
#else
                reinterpret_cast<char *>(*current);
#endif

        if (genericPtr < *heapChunkBoundaries || genericPtr >= *(heapChunkBoundariesEnd - 1))
            continue;
        int index = qLowerBound(heapChunkBoundaries, heapChunkBoundariesEnd, genericPtr) - heapChunkBoundaries;
        // An odd index means the pointer is _before_ the end of a heap chunk and therefore valid.
        if (index & 1) {
            int size = m_d->heapChunks.at(index >> 1).chunkSize;
            Managed *m = reinterpret_cast<Managed *>(genericPtr);
//            qDebug() << "   inside" << size << m;

            if (((quintptr)m - (quintptr)heapChunkBoundaries[index-1]) % size)
                // wrongly aligned value, skip it
                continue;

            if (!m->inUse)
                // Skip pointers to already freed objects, they are bogus as well
                continue;

            m->mark();
//            qDebug() << "       marking";
        }
    }
}
<commit_msg>Fix GC for the last object in a chunk.<commit_after>/****************************************************************************
**
** Copyright (C) 2012 Digia Plc and/or its subsidiary(-ies).
** Contact: http://www.qt-project.org/legal
**
** This file is part of Qt Creator.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and Digia.  For licensing terms and
** conditions see http://qt.digia.com/licensing.  For further information
** use the contact form at http://qt.digia.com/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, 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, Digia gives you certain additional
** rights.  These rights are described in the Digia Qt LGPL Exception
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
****************************************************************************/

#include "qmljs_engine.h"
#include "qv4object.h"
#include "qv4objectproto.h"
#include "qv4mm.h"
#include "PageAllocation.h"
#include "StdLibExtras.h"

#include <QTime>
#include <QVector>
#include <QVector>
#include <QMap>

#include <iostream>
#include <cstdlib>
#include <alloca.h>

using namespace QQmlJS::VM;
using namespace WTF;

static const std::size_t CHUNK_SIZE = 65536;

struct MemoryManager::Data
{
    bool enableGC;
    bool gcBlocked;
    bool scribble;
    bool aggressiveGC;
    ExecutionEngine *engine;

    enum { MaxItemSize = 256 };
    Managed *smallItems[MaxItemSize/16];
    uint nChunks[MaxItemSize/16];
    struct Chunk {
        PageAllocation memory;
        int chunkSize;
    };

    QVector<Chunk> heapChunks;
    QHash<Managed *, uint> protectedObject;

    // statistics:
#ifdef DETAILED_MM_STATS
    QVector<unsigned> allocSizeCounters;
#endif // DETAILED_MM_STATS

    Data(bool enableGC)
        : enableGC(enableGC)
        , gcBlocked(false)
        , engine(0)
    {
        memset(smallItems, 0, sizeof(smallItems));
        memset(nChunks, 0, sizeof(nChunks));
        scribble = !qgetenv("MM_SCRIBBLE").isEmpty();
        aggressiveGC = !qgetenv("MM_AGGRESSIVE_GC").isEmpty();
    }

    ~Data()
    {
        for (QVector<Chunk>::iterator i = heapChunks.begin(), ei = heapChunks.end(); i != ei; ++i)
            i->memory.deallocate();
    }
};

namespace QQmlJS { namespace VM {

bool operator<(const MemoryManager::Data::Chunk &a, const MemoryManager::Data::Chunk &b)
{
    return a.memory.base() < b.memory.base();
}

} } // namespace QQmlJS::VM

MemoryManager::MemoryManager()
    : m_d(new Data(true))
{
    setEnableGC(true);
}

Managed *MemoryManager::alloc(std::size_t size)
{
    if (m_d->aggressiveGC)
        runGC();
#ifdef DETAILED_MM_STATS
    willAllocate(size);
#endif // DETAILED_MM_STATS

    assert(size >= 16);
    assert(size % 16 == 0);

    size_t pos = size >> 4;

    // fits into a small bucket
    assert(size < MemoryManager::Data::MaxItemSize);

    Managed *m = m_d->smallItems[pos];
    if (m)
        goto found;

    // try to free up space, otherwise allocate
    if (!m_d->aggressiveGC) {
        runGC();
        m = m_d->smallItems[pos];
        if (m)
            goto found;
    }

    // no free item available, allocate a new chunk
    {
        // allocate larger chunks at a time to avoid excessive GC, but cap at 64M chunks
        uint shift = ++m_d->nChunks[pos];
        if (shift > 10)
            shift = 10;
        std::size_t allocSize = std::max(size, CHUNK_SIZE*(1 << shift));
        allocSize = roundUpToMultipleOf(WTF::pageSize(), allocSize);
        Data::Chunk allocation;
        allocation.memory = PageAllocation::allocate(allocSize, OSAllocator::JSGCHeapPages);
        allocation.chunkSize = size;
        m_d->heapChunks.append(allocation);
        qSort(m_d->heapChunks);
        char *chunk = (char *)allocation.memory.base();
        char *end = chunk + allocation.memory.size() - size;
        memset(chunk, 0, allocation.memory.size());
        Managed **last = &m_d->smallItems[pos];
        while (chunk <= end) {
            Managed *o = reinterpret_cast<Managed *>(chunk);
            o->inUse = 0;
            o->markBit = 0;
            *last = o;
            last = &o->nextFree;
            chunk += size;
        }
        *last = 0;
        m = m_d->smallItems[pos];
    }

  found:
    m_d->smallItems[pos] = m->nextFree;
    return m;
}

#define SCRIBBLE(obj, c, size) \
    if (m_d->scribble) \
        ::memset((void *)(obj + 1), c, size - sizeof(Managed));

void MemoryManager::mark()
{
    m_d->engine->markObjects();

    for (ExecutionContext *ctxt = engine()->current; ctxt; ctxt = ctxt->parent)
        ctxt->mark();

    for (QHash<Managed *, uint>::const_iterator it = m_d->protectedObject.begin(); it != m_d->protectedObject.constEnd(); ++it)
        it.key()->mark();

    collectFromStack();

    return;
}

std::size_t MemoryManager::sweep()
{
    std::size_t freedCount = 0;

    for (QVector<Data::Chunk>::iterator i = m_d->heapChunks.begin(), ei = m_d->heapChunks.end(); i != ei; ++i)
        freedCount += sweep(reinterpret_cast<char*>(i->memory.base()), i->memory.size(), i->chunkSize);

    return freedCount;
}

std::size_t MemoryManager::sweep(char *chunkStart, std::size_t chunkSize, size_t size)
{
//    qDebug("chunkStart @ %p, size=%x, pos=%x (%x)", chunkStart, size, size>>4, m_d->smallItems[size >> 4]);
    std::size_t freedCount = 0;

    Managed **f = &m_d->smallItems[size >> 4];

    for (char *chunk = chunkStart, *chunkEnd = chunk + chunkSize - size; chunk <= chunkEnd; ) {
        Managed *m = reinterpret_cast<Managed *>(chunk);
//        qDebug("chunk @ %p, size = %lu, in use: %s, mark bit: %s",
//               chunk, m->size, (m->inUse ? "yes" : "no"), (m->markBit ? "true" : "false"));

        assert((intptr_t) chunk % 16 == 0);

        chunk = chunk + size;
        if (m->inUse) {
            if (m->markBit) {
                m->markBit = 0;
            } else {
//                qDebug() << "-- collecting it." << m << *f << &m->nextFree;
                m->~Managed();

                m->nextFree = *f;
                f = &m->nextFree;
                SCRIBBLE(m, 0x99, size);
                ++freedCount;
            }
        }
    }

    return freedCount;
}

bool MemoryManager::isGCBlocked() const
{
    return m_d->gcBlocked;
}

void MemoryManager::setGCBlocked(bool blockGC)
{
    m_d->gcBlocked = blockGC;
}

void MemoryManager::runGC()
{
    if (!m_d->enableGC || m_d->gcBlocked) {
//        qDebug() << "Not running GC.";
        return;
    }

//    QTime t; t.start();

//    qDebug() << ">>>>>>>>runGC";

    mark();
//    std::cerr << "GC: marked " << marks
//              << " objects in " << t.elapsed()
//              << "ms" << std::endl;

//    t.restart();
    /*std::size_t freedCount =*/ sweep();
//    std::cerr << "GC: sweep freed " << freedCount
//              << " objects in " << t.elapsed()
//              << "ms" << std::endl;
}

void MemoryManager::setEnableGC(bool enableGC)
{
    m_d->enableGC = enableGC;
}

MemoryManager::~MemoryManager()
{
    sweep();
}

void MemoryManager::protect(Managed *m)
{
    ++m_d->protectedObject[m];
}

void MemoryManager::unprotect(Managed *m)
{
    if (!--m_d->protectedObject[m])
        m_d->protectedObject.remove(m);
}

static inline void add(QVector<Managed *> &values, const Value &v)
{
    if (Object *o = v.asObject())
        values.append(o);
}

void MemoryManager::setExecutionEngine(ExecutionEngine *engine)
{
    m_d->engine = engine;
}

void MemoryManager::dumpStats() const
{
#ifdef DETAILED_MM_STATS
    std::cerr << "=================" << std::endl;
    std::cerr << "Allocation stats:" << std::endl;
    std::cerr << "Requests for each chunk size:" << std::endl;
    for (int i = 0; i < m_d->allocSizeCounters.size(); ++i) {
        if (unsigned count = m_d->allocSizeCounters[i]) {
            std::cerr << "\t" << (i << 4) << " bytes chunks: " << count << std::endl;
        }
    }
#endif // DETAILED_MM_STATS
}

ExecutionEngine *MemoryManager::engine() const
{
    return m_d->engine;
}

#ifdef DETAILED_MM_STATS
void MemoryManager::willAllocate(std::size_t size)
{
    unsigned alignedSize = (size + 15) >> 4;
    QVector<unsigned> &counters = m_d->allocSizeCounters;
    if ((unsigned) counters.size() < alignedSize + 1)
        counters.resize(alignedSize + 1);
    counters[alignedSize]++;
}

#endif // DETAILED_MM_STATS

void MemoryManager::collectFromStack() const
{
    if (!m_d->heapChunks.count())
        return;

    quintptr valueOnStack = 0;

#if USE(PTHREADS)
#if OS(DARWIN)
    void* stackTop = 0;
    stackTop = pthread_get_stackaddr_np(pthread_self());
    quintptr *top = static_cast<quintptr *>(stackTop);
#else
    void* stackBottom = 0;
    pthread_attr_t attr;
    pthread_getattr_np(pthread_self(), &attr);
    size_t stackSize = 0;
    pthread_attr_getstack(&attr, &stackBottom, &stackSize);
    pthread_attr_destroy(&attr);

    quintptr *top = static_cast<quintptr *>(stackBottom) + stackSize/sizeof(quintptr);
#endif
#endif
//    qDebug() << "stack:" << hex << stackTop << stackSize << (stackTop + stackSize);

    quintptr *current = (&valueOnStack) + 1;
//    qDebug() << "collectFromStack" << top << current << &valueOnStack;

    char** heapChunkBoundaries = (char**)alloca(m_d->heapChunks.count() * 2 * sizeof(char*));
    char** heapChunkBoundariesEnd = heapChunkBoundaries + 2 * m_d->heapChunks.count();
    int i = 0;
    for (QVector<Data::Chunk>::Iterator it = m_d->heapChunks.begin(), end =
         m_d->heapChunks.end(); it != end; ++it) {
        heapChunkBoundaries[i++] = reinterpret_cast<char*>(it->memory.base());
        heapChunkBoundaries[i++] = reinterpret_cast<char*>(it->memory.base()) + it->memory.size() - it->chunkSize;
    }

    for (; current < top; ++current) {
        char* genericPtr =
#if CPU(X86_64)
                reinterpret_cast<char *>((*current) & ~(quint64(Value::Type_Mask) << Value::Tag_Shift));
#else
                reinterpret_cast<char *>(*current);
#endif

        if (genericPtr < *heapChunkBoundaries || genericPtr > *(heapChunkBoundariesEnd - 1))
            continue;
        int index = qLowerBound(heapChunkBoundaries, heapChunkBoundariesEnd, genericPtr) - heapChunkBoundaries;
        // An odd index means the pointer is _before_ the end of a heap chunk and therefore valid.
        if (index & 1) {
            int size = m_d->heapChunks.at(index >> 1).chunkSize;
            Managed *m = reinterpret_cast<Managed *>(genericPtr);
//            qDebug() << "   inside" << size << m;

            if (((quintptr)m - (quintptr)heapChunkBoundaries[index-1]) % size)
                // wrongly aligned value, skip it
                continue;

            if (!m->inUse)
                // Skip pointers to already freed objects, they are bogus as well
                continue;

            m->mark();
//            qDebug() << "       marking";
        }
    }
}
<|endoftext|>


<commit_before>/*
  Licensed under LPGL v2 or later.
  Author: Petr Vanek <petr@yarpen.cz>
 */

#include "workbreak.h"
#include <QtDebug>
#include <QtGui/QMessageBox>
#include <QtGui/QMenu>
#include <QtGui/QApplication>
#include <QtCore/QTimer>
#include <QtCore/QTime>

#ifdef HAVE_QTDBUS
#include <QtDBus/QDBusInterface>
#include <QtDBus/QDBusConnection>
#endif

WorkBreak::WorkBreak(QObject *parent)
    : QSystemTrayIcon(parent)
{

    if (!QFile::exists(FILE_PREFIX))
    {
        m_appPrefix = QCoreApplication::applicationDirPath() + "/";
    }

    m_iconPaths[Init] = m_appPrefix + FILE_PREFIX + "icons/green.png";
    m_iconPaths[Green] = m_appPrefix + FILE_PREFIX + "icons/green.png";
    m_iconPaths[Yellow] = m_appPrefix + FILE_PREFIX + "icons/yellow.png";
    m_iconPaths[Red] = m_appPrefix + FILE_PREFIX + "icons/red.png";

    m_icons[Init] = QIcon(m_iconPaths[Green]);
    m_icons[Green] = QIcon(m_iconPaths[Green]);
    m_icons[Yellow] = QIcon(m_iconPaths[Yellow]);
    m_icons[Red] = QIcon(m_iconPaths[Red]);

    qApp->setWindowIcon(m_icons[Green]);

    QTime initial(0, 0);
    int intervals = 14;
    for (int i = 0; i < intervals; ++i)
        m_schedule << initial.addSecs(i * (24*60*60/intervals));
    qSort(m_schedule.begin(), m_schedule.end());
    qDebug() << "Schedule:" << m_schedule;

    setStatus(Init);
    setToolTip(tr("Application just started. Calculating schedule..."));

#ifdef HAVE_QTDBUS
    m_interface = new OrgFreedesktopNotificationsInterface("org.freedesktop.Notifications",
                                                           "/org/freedesktop/Notifications",
                                                           QDBusConnection::sessionBus());
    connect(m_interface, SIGNAL(NotificationClosed(uint, uint)),
           this, SLOT(notificationClosed(uint,uint)));
    connect(m_interface, SIGNAL(ActionInvoked(uint,QString)),
            this, SLOT(handleAction(uint,QString)));
#endif

    // context menu
    QMenu *menu = new QMenu(tr("Actions"));
    menu->addAction(QIcon::fromTheme("help-about"), tr("About..."), this, SLOT(about()));
    menu->addAction(QIcon::fromTheme("application-exit"), tr("Quit"), qApp, SLOT(quit()));
    setContextMenu(menu);

    m_timer = new QTimer(this);
    connect(m_timer, SIGNAL(timeout()), this, SLOT(timerTimeout()));
    // minute interval
    m_timer->setInterval(60000);
    timerTimeout();
}

WorkBreak::~WorkBreak()
{
    contextMenu()->deleteLater();
}

void WorkBreak::setStatus(Status status)
{
    m_status = status;
    setIcon(m_icons[status]);
}

void WorkBreak::timerTimeout()
{
    qDebug() << "void WorkBreak::timerTimeout()";
    m_timer->stop();

    QTime current = QTime::currentTime();
    QTime nextSchedule;

    // find the nearest next event
    foreach (QTime t, m_schedule)
    {
        if (t > current && !m_scheduleDone.contains(t))
        {
            nextSchedule = t;
            break;
        }
        else
            continue;
    }

    if (nextSchedule.isNull())
    {
        qDebug() << "No next schedule found";
        m_scheduleDone.clear();
    }
    else
    {
        qDebug() << "Next trigger @" << nextSchedule << "current:" << current;

        int interval = current.secsTo(nextSchedule);
        if (interval < 59) // main alert
        {
            setStatus(Red);
            message(tr("A work break needed!"), tr("Take a rest for few minutes."));
            m_scheduleDone.append(nextSchedule);
            setToolTip(tr("Take a rest!"));
        }
        else if (interval < 1600) // warm-up icon
        {
            setStatus(Yellow);
            setToolTip(tr("Next break in %1 minute(s)").arg(interval/60));
        }
        else
        {
            if (m_status != Green)
            {
                setStatus(Green);
                setToolTip(tr("You are fresh. Continue to work..."));
            }
        }
    }

    m_timer->start();
}

void WorkBreak::message(const QString &title,
                        const QString &body)
{
    qDebug() << "Notification to show:" << title << body;
    if (freedesktopNotify(title, body))
    {
        return;
    }
    else if (QSystemTrayIcon::supportsMessages())
    {
        qDebug() << "No freedesktop dbus service available. Trying tray message.";
        showMessage(title, body, QSystemTrayIcon::Warning, 100000);
    }
    else
    {
        qDebug() << "No appropriate message method found. Using modal dialog.";
        QMessageBox msg(QMessageBox::Warning, title, body, QMessageBox::Close);
        msg.setIconPixmap(m_icons[m_status].pixmap(64));
        msg.exec();
    }
}

bool WorkBreak::freedesktopNotify(const QString &title, const QString &summary)
{
#ifdef HAVE_QTDBUS
    if (m_interface->lastError().isValid())
    {
        qDebug() << "FD01" << m_interface->lastError().message();
        return false;
    }

    QDBusPendingReply<uint> reply = m_interface->Notify(qApp->applicationName(), // app_name
                                                        0, // replaces_id - server assigns ID, we will just ignore it
                                                        m_iconPaths[m_status], // app_icon
                                                        title, // summary
                                                        summary, // body
                                                        QStringList() << "default" << tr("Yes, I'm taking a break!"), // actions
                                                        QVariantMap(), // hints
                                                        0); // expire_timeout = never

    reply.waitForFinished();
    if (reply.isError())
    {
        qDebug() << "FD02" << reply.error().message();
        return false;
    }
    qDebug() << "Notification ID: "<< reply.value();
    return true;
#else
    return false;
#endif
}

#ifdef HAVE_QTDBUS
void WorkBreak::notificationClosed(uint, uint)
{
    timerTimeout();
}

void WorkBreak::handleAction(uint id, QString)
{
    m_interface->CloseNotification(id);
}
#endif

void WorkBreak::about()
{
    QMessageBox::about(0, tr("About Work Break"),
                       tr("A reminder for regular short breaks while you are working on your computer. "
                          "
Read more info in separate documentation.
"
                          "
(c) 2012 Petr Vanek petr@yarpen.cz
"
                          "
License: LGPL2+
"
                          "
Version: %2
"
                         ).arg("file://" + m_appPrefix + FILE_PREFIX + "index_en.html").arg(APP_VERSION)
                      );
}

<commit_msg>fixed scheduler<commit_after>/*
  Licensed under LPGL v2 or later.
  Author: Petr Vanek <petr@yarpen.cz>
 */

#include "workbreak.h"
#include <QtDebug>
#include <QtGui/QMessageBox>
#include <QtGui/QMenu>
#include <QtGui/QApplication>
#include <QtCore/QTimer>
#include <QtCore/QTime>

#ifdef HAVE_QTDBUS
#include <QtDBus/QDBusInterface>
#include <QtDBus/QDBusConnection>
#endif

WorkBreak::WorkBreak(QObject *parent)
    : QSystemTrayIcon(parent)
{

    if (!QFile::exists(FILE_PREFIX))
    {
        m_appPrefix = QCoreApplication::applicationDirPath() + "/";
    }

    m_iconPaths[Init] = m_appPrefix + FILE_PREFIX + "icons/green.png";
    m_iconPaths[Green] = m_appPrefix + FILE_PREFIX + "icons/green.png";
    m_iconPaths[Yellow] = m_appPrefix + FILE_PREFIX + "icons/yellow.png";
    m_iconPaths[Red] = m_appPrefix + FILE_PREFIX + "icons/red.png";

    m_icons[Init] = QIcon(m_iconPaths[Green]);
    m_icons[Green] = QIcon(m_iconPaths[Green]);
    m_icons[Yellow] = QIcon(m_iconPaths[Yellow]);
    m_icons[Red] = QIcon(m_iconPaths[Red]);

    qApp->setWindowIcon(m_icons[Green]);

    QTime initial(0, 0);
    int intervals = 14;
    for (int i = 0; i < intervals; ++i)
        m_schedule << initial.addSecs(i * (24*60*60/intervals));
    // midnight failback showstopper
    m_schedule << QTime(23, 59, 59);
    qSort(m_schedule.begin(), m_schedule.end());
    qDebug() << "Schedule:" << m_schedule;

    setStatus(Init);
    setToolTip(tr("Application just started. Calculating schedule..."));

#ifdef HAVE_QTDBUS
    m_interface = new OrgFreedesktopNotificationsInterface("org.freedesktop.Notifications",
                                                           "/org/freedesktop/Notifications",
                                                           QDBusConnection::sessionBus());
    connect(m_interface, SIGNAL(NotificationClosed(uint, uint)),
           this, SLOT(notificationClosed(uint,uint)));
    connect(m_interface, SIGNAL(ActionInvoked(uint,QString)),
            this, SLOT(handleAction(uint,QString)));
#endif

    // context menu
    QMenu *menu = new QMenu(tr("Actions"));
    menu->addAction(QIcon::fromTheme("help-about"), tr("About..."), this, SLOT(about()));
    menu->addAction(QIcon::fromTheme("application-exit"), tr("Quit"), qApp, SLOT(quit()));
    setContextMenu(menu);

    m_timer = new QTimer(this);
    connect(m_timer, SIGNAL(timeout()), this, SLOT(timerTimeout()));
    // minute interval
    m_timer->setInterval(60000);
    timerTimeout();
}

WorkBreak::~WorkBreak()
{
    contextMenu()->deleteLater();
}

void WorkBreak::setStatus(Status status)
{
    m_status = status;
    setIcon(m_icons[status]);
}

void WorkBreak::timerTimeout()
{
    qDebug() << "void WorkBreak::timerTimeout()";
    m_timer->stop();

    QTime current = QTime::currentTime();
    QTime nextSchedule;

    // find the nearest next event
    foreach (QTime t, m_schedule)
    {
        if (t > current && !m_scheduleDone.contains(t))
        {
            nextSchedule = t;
            break;
        }
        else
            continue;
    }

    if (nextSchedule.isNull())
    {
        qDebug() << "No next schedule found.";
        m_scheduleDone.clear();
    }
    else
    {
        qDebug() << "Next trigger @" << nextSchedule << "current:" << current;

        int interval = current.secsTo(nextSchedule);
        if (interval < 59) // main alert
        {
            setStatus(Red);
            message(tr("A work break needed!"), tr("Take a rest for few minutes."));
            m_scheduleDone.append(nextSchedule);
            setToolTip(tr("Take a rest!"));
        }
        else if (interval < 1600) // warm-up icon
        {
            setStatus(Yellow);
            setToolTip(tr("Next break in %1 minute(s)").arg(interval/60));
        }
        else
        {
            if (m_status != Green)
            {
                setStatus(Green);
                setToolTip(tr("You are fresh. Continue to work..."));
            }
        }
    }

    m_timer->start();
}

void WorkBreak::message(const QString &title,
                        const QString &body)
{
    qDebug() << "Notification to show:" << title << body;
    if (freedesktopNotify(title, body))
    {
        return;
    }
    else if (QSystemTrayIcon::supportsMessages())
    {
        qDebug() << "No freedesktop dbus service available. Trying tray message.";
        showMessage(title, body, QSystemTrayIcon::Warning, 100000);
    }
    else
    {
        qDebug() << "No appropriate message method found. Using modal dialog.";
        QMessageBox msg(QMessageBox::Warning, title, body, QMessageBox::Close);
        msg.setIconPixmap(m_icons[m_status].pixmap(64));
        msg.exec();
    }
}

bool WorkBreak::freedesktopNotify(const QString &title, const QString &summary)
{
#ifdef HAVE_QTDBUS
    if (m_interface->lastError().isValid())
    {
        qDebug() << "FD01" << m_interface->lastError().message();
        return false;
    }

    QDBusPendingReply<uint> reply = m_interface->Notify(qApp->applicationName(), // app_name
                                                        0, // replaces_id - server assigns ID, we will just ignore it
                                                        m_iconPaths[m_status], // app_icon
                                                        title, // summary
                                                        summary, // body
                                                        QStringList() << "default" << tr("Yes, I'm taking a break!"), // actions
                                                        QVariantMap(), // hints
                                                        0); // expire_timeout = never

    reply.waitForFinished();
    if (reply.isError())
    {
        qDebug() << "FD02" << reply.error().message();
        return false;
    }
    qDebug() << "Notification ID: "<< reply.value();
    return true;
#else
    return false;
#endif
}

#ifdef HAVE_QTDBUS
void WorkBreak::notificationClosed(uint, uint)
{
    timerTimeout();
}

void WorkBreak::handleAction(uint id, QString)
{
    m_interface->CloseNotification(id);
}
#endif

void WorkBreak::about()
{
    QMessageBox::about(0, tr("About Work Break"),
                       tr("A reminder for regular short breaks while you are working on your computer. "
                          "
Read more info in separate documentation.
"
                          "
(c) 2012 Petr Vanek petr@yarpen.cz
"
                          "
License: LGPL2+
"
                          "
Version: %2
"
                         ).arg("file://" + m_appPrefix + FILE_PREFIX + "index_en.html").arg(APP_VERSION)
                      );
}

<|endoftext|>


<commit_before>#include "var/Vector.hpp"
#include "var/Json.hpp"
#include "sys/Sys.hpp"
#include "sys/requests.h"

#if defined __link
#define atoff atof
#endif

using namespace var;

JsonApi JsonValue::m_api;

JsonValue::JsonValue(){
	if( api().is_valid() == false ){ exit_fatal("json api missing"); }
	m_value = 0; //create() method from children are not available in the constructor
}

JsonValue::JsonValue(json_t * value){
	if( api().is_valid() == false ){ exit_fatal("json api missing"); }
	add_reference(value);
}

JsonValue::JsonValue(const JsonValue & value){
	if( api().is_valid() == false ){ exit_fatal("json api missing"); }
	add_reference(value.m_value);
}

JsonValue & JsonValue::operator=(const JsonValue & value){
	api()->decref(m_value);
	add_reference(value.m_value);
	return *this;
}

void JsonValue::add_reference(json_t * value){
	m_value = value;
	api()->incref(value);
}

JsonValue::JsonValue(JsonValue && a){
	m_value = a.m_value;
	a.m_value = 0;
}

JsonValue& JsonValue::operator=(JsonValue && a){
	m_value = a.m_value;
	a.m_value = 0;
	return *this;
}

JsonValue::~JsonValue(){
	//only decref if object was create (not just a reference)
	api()->decref(m_value);
	m_value = 0;
}

const JsonObject & JsonValue::to_object() const {
	return (const JsonObject &)*this;
}

JsonObject & JsonValue::to_object(){
	return (JsonObject&)*this;
}

const JsonArray & JsonValue::to_array() const {
	return (const JsonArray &)*this;
}

JsonArray & JsonValue::to_array(){
	return (JsonArray &)*this;
}

int JsonValue::create_if_not_valid(){
	if( is_valid() ){ return 0; }
	m_value = create();
	if( m_value == 0 ){ return -1; }
	return 0;
}

int JsonValue::assign(const var::ConstString & value){
	if( is_string() ){
		return api()->string_set(m_value, value.str());
	} else if( is_real() ){
		return api()->real_set(m_value, ::atoff(value.str()));
	} else if( is_integer() ){
		return api()->integer_set(m_value, ::atoi(value.str()));
	} else if ( is_true() ){
		if( value == "false" ){

		}
	}

	//can't assign string to object, array, bool, or null

	return -1;
}

int JsonValue::assign(float value){
	return 0;
}

int JsonValue::assign(int value){
	return 0;
}

int JsonValue::assign(bool value){
	return 0;
}

int JsonValue::copy(const JsonValue & value, bool is_deep){
	api()->decref(m_value);
	if( is_deep ){
		m_value = api()->deep_copy(value.m_value);
	} else {
		m_value = api()->copy(value.m_value);
	}
	return 0;
}

var::String JsonValue::to_string() const {
	var::String result;
	if( is_string() ){
		result = api()->string_value(m_value);
	} else if( is_real() ){
		result.format("%f", api()->real_value(m_value));
	} else if (is_integer() ){
		result.format("%ld", api()->integer_value(m_value));
	} else if( is_true() ){
		result = "true";
	} else if( is_false() ){
		result = "false";
	} else if( is_null() ){
		result = "null";
	} else if( is_object() ){
		result = "{object}";
	} else if ( is_array() ){
		result = "[array]";
	} else {
		result = "<invalid>";
	}
	return result;
}

float JsonValue::to_float() const {
	if( is_string() ){
		return to_string().to_float();
	}
	return api()->real_value(m_value);
}

int JsonValue::to_integer() const {
	if( is_string() ){
		return to_string().to_integer();
	}
	return api()->integer_value(m_value);
}

bool JsonValue::to_bool() const {
	if( is_true() ){ return true; }
	if( is_false() ){ return false; }

	if( is_string() ){
		var::String s = to_string();
		s.to_upper();
		if( s == "TRUE" ){ return true; }
		return false;
	}

	if( is_integer() ){ return to_integer() != 0; }
	if( is_real() ){ return to_float() != 0.0f; }
	if( is_object() ){ return true; }
	if( is_array() ){ return true; }
	return false;
}

JsonObject::JsonObject(){
	m_value = create();
}

JsonObject::JsonObject(const JsonObject & value){
	add_reference(value.m_value);
}

JsonObject & JsonObject::operator=(const JsonObject & value){
	api()->decref(m_value);
	add_reference(value.m_value);
	return *this;
}

json_t * JsonObject::create(){
	return api()->create_object();
}

json_t * JsonArray::create(){
	return api()->create_array();
}

json_t * JsonReal::create(){
	return api()->create_real(0.0f);
}

json_t * JsonInteger::create(){
	return api()->create_integer(0);
}

json_t * JsonTrue::create(){
	return api()->create_true();
}

json_t * JsonFalse::create(){
	return api()->create_false();
}

json_t * JsonString::create(){
	return api()->create_string("");
}

json_t * JsonNull::create(){
	return api()->create_null();
}

int JsonObject::insert(const var::ConstString & key, const JsonValue & value){
	if( create_if_not_valid() < 0 ){
		return -1;
	}

	int result = api()->object_set(m_value, key.str(), value.m_value);
	if( result < 0 ){
		//printf("Failed to set JSON key %s to %s %p\n", key.str(), value.to_string().str(), m_value);
	}
	return result;
}

int JsonObject::update(const JsonValue & value, u8 o_flags){
	if( o_flags & UPDATE_EXISTING ){
		return api()->object_update_existing(m_value, value.m_value);
	}

	if( o_flags & UPDATE_MISSING ){
		return api()->object_update_missing(m_value, value.m_value);
	}

	return api()->object_update(m_value, value.m_value);
}

int JsonObject::remove(const var::ConstString & key){
	return api()->object_del(m_value, key.str());
}

u32 JsonObject::count() const {
	return api()->object_size(m_value);
}

int JsonObject::clear(){ return json_object_clear(m_value); }

var::Vector<var::String> JsonObject::keys() const {
	const char *key;
	json_t *value;
	var::Vector<var::String> result;

	for(key = api()->object_iter_key(api()->object_iter(m_value));
		 key && (value = api()->object_iter_value(api()->object_key_to_iter(key)));
		 key = api()->object_iter_key(api()->object_iter_next(m_value, api()->object_key_to_iter(key)))){
		result.push_back(var::String(key));
	}

	return result;
}

JsonValue JsonObject::at(const var::ConstString & key) const {
	return api()->object_get(m_value, key.str());
}

JsonArray::JsonArray(){
	m_value = create();
}

JsonArray::JsonArray(const JsonArray & value){
	add_reference(value.m_value);
}

JsonArray & JsonArray::operator=(const JsonArray & value){
	api()->decref(m_value);
	add_reference(value.m_value);
	return *this;
}

u32 JsonArray::count() const {
	return api()->array_size(m_value);
}

JsonValue JsonArray::at(u32 idx) const {
	return api()->array_get(m_value, idx);
}

int JsonArray::append(const JsonValue & value){
	if( create_if_not_valid() < 0 ){ return -1; }
	return api()->array_append(m_value, value.m_value);
}

int JsonArray::append(const JsonArray & array){
	if( create_if_not_valid() < 0 ){ return -1; }
	return api()->array_extend(m_value, array.m_value);
}

int JsonArray::insert(u32 idx, const JsonValue & value){
	if( create_if_not_valid() < 0 ){ return -1; }
	return api()->array_insert(m_value, idx, value.m_value);
}

int JsonArray::remove(u32 idx){
	return api()->array_remove(m_value, idx);
}

int JsonArray::clear(){
	return api()->array_clear(m_value);
}

JsonString::JsonString(){
	m_value = create();
}

JsonString::JsonString(const var::ConstString & str){
	m_value = api()->create_string(str.str());
}

JsonReal::JsonReal(){
	m_value = create();
}

JsonReal::JsonReal(float value){
	m_value = api()->create_real(value);
}

JsonReal & JsonReal::operator=(float a){
	if( create_if_not_valid() == 0 ){
		api()->real_set(m_value, a);
	}
	return *this;
}

JsonInteger::JsonInteger(){
	m_value = create();
}

JsonInteger::JsonInteger(int value){
	m_value = api()->create_integer(value);
}

JsonInteger & JsonInteger::operator=(int a){
	if( create_if_not_valid() == 0 ){
		api()->integer_set(m_value, a);
	}
	return *this;
}

JsonNull::JsonNull(){
	m_value = create();
}

JsonTrue::JsonTrue(){
	m_value = create();
}

JsonFalse::JsonFalse(){
	m_value = create();
}

JsonValue JsonDocument::load_from_file(const var::ConstString & path){
	JsonValue value;
	value.m_value = JsonValue::api()->load_file(path.str(), flags(), &m_error.m_value);
	return value;
}

JsonValue JsonDocument::load_from_string(const var::ConstString & json){
	JsonValue value;
	value.m_value = JsonValue::api()->loadb(json.str(), json.length(), flags(), &m_error.m_value);
	return value;
}

//only use on Stratify OS
JsonValue JsonDocument::load_from_file(const sys::File & file){
	JsonValue value;
	value.m_value = JsonValue::api()->loadfd(file.fileno(), flags(), &m_error.m_value);
	return value;
}

JsonValue JsonDocument::load(json_load_callback_t callback, void * context){
	JsonValue value;
	value.m_value = JsonValue::api()->load_callback(callback, context, flags(), &m_error.m_value);
	return value;
}

int JsonDocument::save_to_file(const JsonValue & value, const var::ConstString & path) const {
	return JsonValue::api()->dump_file(value.m_value, path.str(), flags());
}

var::String JsonDocument::stringify(const JsonValue & value) const {
	u32 size = JsonValue::api()->dumpb(value.m_value, 0, 0, flags());
	if( size == 0 ){
		return var::String();
	}
	var::String result;
	if( result.set_capacity(size) < 0 ){
		return var::String();
	}
	if( JsonValue::api()->dumpb(value.m_value, result.to<char>(), result.capacity(), flags()) == 0 ){
		return var::String();
	}
	return result;
}

int JsonDocument::save_to_file(const JsonValue & value, const sys::File & file) const {
	return JsonValue::api()->dumpfd(value.m_value, file.fileno(), flags());
}

int JsonDocument::save(const JsonValue & value, json_dump_callback_t callback, void * context) const {
	return JsonValue::api()->dump_callback(value.m_value, callback, context, flags());
}

<commit_msg>Json save to file uses posix not fopen()<commit_after>#include "var/Vector.hpp"
#include "var/Json.hpp"
#include "sys/Sys.hpp"
#include "sys/requests.h"

#if defined __link
#define atoff atof
#endif

using namespace var;

JsonApi JsonValue::m_api;

JsonValue::JsonValue(){
	if( api().is_valid() == false ){ exit_fatal("json api missing"); }
	m_value = 0; //create() method from children are not available in the constructor
}

JsonValue::JsonValue(json_t * value){
	if( api().is_valid() == false ){ exit_fatal("json api missing"); }
	add_reference(value);
}

JsonValue::JsonValue(const JsonValue & value){
	if( api().is_valid() == false ){ exit_fatal("json api missing"); }
	add_reference(value.m_value);
}

JsonValue & JsonValue::operator=(const JsonValue & value){
	api()->decref(m_value);
	add_reference(value.m_value);
	return *this;
}

void JsonValue::add_reference(json_t * value){
	m_value = value;
	api()->incref(value);
}

JsonValue::JsonValue(JsonValue && a){
	m_value = a.m_value;
	a.m_value = 0;
}

JsonValue& JsonValue::operator=(JsonValue && a){
	m_value = a.m_value;
	a.m_value = 0;
	return *this;
}

JsonValue::~JsonValue(){
	//only decref if object was create (not just a reference)
	api()->decref(m_value);
	m_value = 0;
}

const JsonObject & JsonValue::to_object() const {
	return (const JsonObject &)*this;
}

JsonObject & JsonValue::to_object(){
	return (JsonObject&)*this;
}

const JsonArray & JsonValue::to_array() const {
	return (const JsonArray &)*this;
}

JsonArray & JsonValue::to_array(){
	return (JsonArray &)*this;
}

int JsonValue::create_if_not_valid(){
	if( is_valid() ){ return 0; }
	m_value = create();
	if( m_value == 0 ){ return -1; }
	return 0;
}

int JsonValue::assign(const var::ConstString & value){
	if( is_string() ){
		return api()->string_set(m_value, value.cstring());
	} else if( is_real() ){
		return api()->real_set(m_value, ::atoff(value.cstring()));
	} else if( is_integer() ){
		return api()->integer_set(m_value, ::atoi(value.cstring()));
	} else if ( is_true() ){
		if( value == "false" ){

		}
	}

	//can't assign string to object, array, bool, or null

	return -1;
}

int JsonValue::assign(float value){
	return 0;
}

int JsonValue::assign(int value){
	return 0;
}

int JsonValue::assign(bool value){
	return 0;
}

int JsonValue::copy(const JsonValue & value, bool is_deep){
	api()->decref(m_value);
	if( is_deep ){
		m_value = api()->deep_copy(value.m_value);
	} else {
		m_value = api()->copy(value.m_value);
	}
	return 0;
}

var::String JsonValue::to_string() const {
	var::String result;
	if( is_string() ){
		result = api()->string_value(m_value);
	} else if( is_real() ){
		result.format("%f", api()->real_value(m_value));
	} else if (is_integer() ){
		result.format("%ld", api()->integer_value(m_value));
	} else if( is_true() ){
		result = "true";
	} else if( is_false() ){
		result = "false";
	} else if( is_null() ){
		result = "null";
	} else if( is_object() ){
		result = "{object}";
	} else if ( is_array() ){
		result = "[array]";
	} else {
		result = "<invalid>";
	}
	return result;
}

float JsonValue::to_float() const {
	if( is_string() ){
		return to_string().to_float();
	}
	return api()->real_value(m_value);
}

int JsonValue::to_integer() const {
	if( is_string() ){
		return to_string().to_integer();
	}
	return api()->integer_value(m_value);
}

bool JsonValue::to_bool() const {
	if( is_true() ){ return true; }
	if( is_false() ){ return false; }

	if( is_string() ){
		var::String s = to_string();
		s.to_upper();
		if( s == "TRUE" ){ return true; }
		return false;
	}

	if( is_integer() ){ return to_integer() != 0; }
	if( is_real() ){ return to_float() != 0.0f; }
	if( is_object() ){ return true; }
	if( is_array() ){ return true; }
	return false;
}

JsonObject::JsonObject(){
	m_value = create();
}

JsonObject::JsonObject(const JsonObject & value){
	add_reference(value.m_value);
}

JsonObject & JsonObject::operator=(const JsonObject & value){
	api()->decref(m_value);
	add_reference(value.m_value);
	return *this;
}

json_t * JsonObject::create(){
	return api()->create_object();
}

json_t * JsonArray::create(){
	return api()->create_array();
}

json_t * JsonReal::create(){
	return api()->create_real(0.0f);
}

json_t * JsonInteger::create(){
	return api()->create_integer(0);
}

json_t * JsonTrue::create(){
	return api()->create_true();
}

json_t * JsonFalse::create(){
	return api()->create_false();
}

json_t * JsonString::create(){
	return api()->create_string("");
}

json_t * JsonNull::create(){
	return api()->create_null();
}

int JsonObject::insert(const var::ConstString & key, const JsonValue & value){
	if( create_if_not_valid() < 0 ){
		return -1;
	}

	int result = api()->object_set(m_value, key.cstring(), value.m_value);
	if( result < 0 ){
		//printf("Failed to set JSON key %s to %s %p\n", key.cstring(), value.to_string().cstring(), m_value);
	}
	return result;
}

int JsonObject::update(const JsonValue & value, u8 o_flags){
	if( o_flags & UPDATE_EXISTING ){
		return api()->object_update_existing(m_value, value.m_value);
	}

	if( o_flags & UPDATE_MISSING ){
		return api()->object_update_missing(m_value, value.m_value);
	}

	return api()->object_update(m_value, value.m_value);
}

int JsonObject::remove(const var::ConstString & key){
	return api()->object_del(m_value, key.cstring());
}

u32 JsonObject::count() const {
	return api()->object_size(m_value);
}

int JsonObject::clear(){ return json_object_clear(m_value); }

var::Vector<var::String> JsonObject::keys() const {
	const char *key;
	json_t *value;
	var::Vector<var::String> result;

	for(key = api()->object_iter_key(api()->object_iter(m_value));
		 key && (value = api()->object_iter_value(api()->object_key_to_iter(key)));
		 key = api()->object_iter_key(api()->object_iter_next(m_value, api()->object_key_to_iter(key)))){
		result.push_back(var::String(key));
	}

	return result;
}

JsonValue JsonObject::at(const var::ConstString & key) const {
	return api()->object_get(m_value, key.cstring());
}

JsonArray::JsonArray(){
	m_value = create();
}

JsonArray::JsonArray(const JsonArray & value){
	add_reference(value.m_value);
}

JsonArray & JsonArray::operator=(const JsonArray & value){
	api()->decref(m_value);
	add_reference(value.m_value);
	return *this;
}

u32 JsonArray::count() const {
	return api()->array_size(m_value);
}

JsonValue JsonArray::at(u32 idx) const {
	return api()->array_get(m_value, idx);
}

int JsonArray::append(const JsonValue & value){
	if( create_if_not_valid() < 0 ){ return -1; }
	return api()->array_append(m_value, value.m_value);
}

int JsonArray::append(const JsonArray & array){
	if( create_if_not_valid() < 0 ){ return -1; }
	return api()->array_extend(m_value, array.m_value);
}

int JsonArray::insert(u32 idx, const JsonValue & value){
	if( create_if_not_valid() < 0 ){ return -1; }
	return api()->array_insert(m_value, idx, value.m_value);
}

int JsonArray::remove(u32 idx){
	return api()->array_remove(m_value, idx);
}

int JsonArray::clear(){
	return api()->array_clear(m_value);
}

JsonString::JsonString(){
	m_value = create();
}

JsonString::JsonString(const var::ConstString & str){
	m_value = api()->create_string(str.cstring());
}

JsonReal::JsonReal(){
	m_value = create();
}

JsonReal::JsonReal(float value){
	m_value = api()->create_real(value);
}

JsonReal & JsonReal::operator=(float a){
	if( create_if_not_valid() == 0 ){
		api()->real_set(m_value, a);
	}
	return *this;
}

JsonInteger::JsonInteger(){
	m_value = create();
}

JsonInteger::JsonInteger(int value){
	m_value = api()->create_integer(value);
}

JsonInteger & JsonInteger::operator=(int a){
	if( create_if_not_valid() == 0 ){
		api()->integer_set(m_value, a);
	}
	return *this;
}

JsonNull::JsonNull(){
	m_value = create();
}

JsonTrue::JsonTrue(){
	m_value = create();
}

JsonFalse::JsonFalse(){
	m_value = create();
}

JsonValue JsonDocument::load_from_file(const var::ConstString & path){
	JsonValue value;
	value.m_value = JsonValue::api()->load_file(path.cstring(), flags(), &m_error.m_value);
	return value;
}

JsonValue JsonDocument::load_from_string(const var::ConstString & json){
	JsonValue value;
	value.m_value = JsonValue::api()->loadb(json.cstring(), json.length(), flags(), &m_error.m_value);
	return value;
}

//only use on Stratify OS
JsonValue JsonDocument::load_from_file(const sys::File & file){
	JsonValue value;
	value.m_value = JsonValue::api()->loadfd(file.fileno(), flags(), &m_error.m_value);
	return value;
}

JsonValue JsonDocument::load(json_load_callback_t callback, void * context){
	JsonValue value;
	value.m_value = JsonValue::api()->load_callback(callback, context, flags(), &m_error.m_value);
	return value;
}

int JsonDocument::save_to_file(const JsonValue & value, const var::ConstString & path) const {
	sys::File f;
	int result;
	if( f.create(path) < 0 ){
		set_error_number(f.error_number());
		return -1;
	}
	result = JsonValue::api()->dumpfd(value.m_value, f.fileno(), flags());

	if( f.close() < 0 ){
		set_error_number(f.error_number());
		return -1;
	}

	return result;
}

var::String JsonDocument::stringify(const JsonValue & value) const {
	u32 size = JsonValue::api()->dumpb(value.m_value, 0, 0, flags());
	if( size == 0 ){
		return var::String();
	}
	var::String result;
	if( result.set_capacity(size) < 0 ){
		return var::String();
	}
	if( JsonValue::api()->dumpb(value.m_value, result.to<char>(), result.capacity(), flags()) == 0 ){
		return var::String();
	}
	return result;
}

int JsonDocument::save_to_file(const JsonValue & value, const sys::File & file) const {
	return JsonValue::api()->dumpfd(value.m_value, file.fileno(), flags());
}

int JsonDocument::save(const JsonValue & value, json_dump_callback_t callback, void * context) const {
	return JsonValue::api()->dump_callback(value.m_value, callback, context, flags());
}

<|endoftext|>


<commit_before>/*
 * Copyright (c) 2012-2016 Daniele Bartolini and individual contributors.
 * License: https://github.com/taylor001/crown/blob/master/LICENSE
 */

#include "debug_line.h"
#include "math_utils.h"
#include "color4.h"
#include "vector3.h"
#include "matrix4x4.h"
#include "error.h"
#include "device.h"
#include <string.h> // memcpy

namespace crown
{

DebugLine::DebugLine(bool depth_test)
	: _marker(MARKER)
	: _shader(depth_test ? "debug_line" : "debug_line_noz")
	, _num(0)
{
	_vertex_decl.begin()
		.add(bgfx::Attrib::Position, 3, bgfx::AttribType::Float)
		.add(bgfx::Attrib::Color0,   4, bgfx::AttribType::Uint8, true)
		.end();
}

DebugLine::~DebugLine()
{
	_marker = 0;
}

void DebugLine::add_line(const Vector3& start, const Vector3& end, const Color4& color)
{
	if (_num >= CROWN_MAX_DEBUG_LINES)
		 return;

	_lines[_num].p0 = start;
	_lines[_num].c0 = to_abgr(color);
	_lines[_num].p1 = end;
	_lines[_num].c1 = to_abgr(color);

	++_num;
}

void DebugLine::add_axes(const Matrix4x4& m, float length)
{
	const Vector3 pos = translation(m);
	add_line(pos, pos + x(m)*length, COLOR4_RED);
	add_line(pos, pos + y(m)*length, COLOR4_GREEN);
	add_line(pos, pos + z(m)*length, COLOR4_BLUE);
}

void DebugLine::add_circle(const Vector3& center, float radius, const Vector3& normal, const Color4& color, uint32_t segments)
{
	const Vector3 dir = normal;
	const Vector3 arr[] =
	{
		{ dir.z, dir.z, -dir.x -dir.y },
		{ -dir.y -dir.z, dir.x, dir.x }
	};
	const int idx = ((dir.z != 0.0f) && (-dir.x != dir.y));
	Vector3 right = arr[idx];
	normalize(right);

	const float incr = 360.0f / (float)(segments >= 3 ? segments : 3);
	float deg0 = 0.0f;
	for (uint32_t ss = 0; ss < segments; ++ss, deg0 += incr)
	{
		const float rad0 = to_rad(deg0);
		const float rad1 = to_rad(deg0 + incr);

		const Vector3 from0 = right*cos(-rad0) + cross(dir, right)*sin(-rad0) + dir*dot(dir, right)*(1.0f-cos(-rad0));
		const Vector3 from1 = right*cos(-rad1) + cross(dir, right)*sin(-rad1) + dir*dot(dir, right)*(1.0f-cos(-rad1));

		add_line(center + radius*from0, center + radius*from1, color);
	}
}

void DebugLine::add_cone(const Vector3& from, const Vector3& to, float radius, const Color4& color, uint32_t segments)
{
	Vector3 dir = to - from;
	normalize(dir);
	const Vector3 arr[] =
	{
		{ dir.z, dir.z, -dir.x -dir.y },
		{ -dir.y -dir.z, dir.x, dir.x }
	};
	const int idx = ((dir.z != 0.0f) && (-dir.x != dir.y));
	Vector3 right = arr[idx];
	normalize(right);

	const float incr = 360.0f / (float)(segments >= 3 ? segments : 3);
	float deg0 = 0.0f;
	for (uint32_t ss = 0; ss < segments; ++ss, deg0 += incr)
	{
		const float rad0 = to_rad(deg0);
		const float rad1 = to_rad(deg0 + incr);

		const Vector3 from0 = right*cos(-rad0) + cross(dir, right)*sin(-rad0) + dir*dot(dir, right)*(1.0f-cos(-rad0));
		const Vector3 from1 = right*cos(-rad1) + cross(dir, right)*sin(-rad1) + dir*dot(dir, right)*(1.0f-cos(-rad1));

		add_line(from + radius*from0, to, color);
		add_line(from + radius*from0, from + radius*from1, color);
	}
}

void DebugLine::add_sphere(const Vector3& center, const float radius, const Color4& color, uint32_t segments)
{
	add_circle(center, radius, VECTOR3_XAXIS, color, segments);
	add_circle(center, radius, VECTOR3_YAXIS, color, segments);
	add_circle(center, radius, VECTOR3_ZAXIS, color, segments);
}

void DebugLine::add_obb(const Matrix4x4& tm, const Vector3& half_extents, const Color4& color)
{
	const Vector3 o = vector3(tm.t.x, tm.t.y, tm.t.z);
	const Vector3 x = vector3(tm.x.x, tm.x.y, tm.x.z) * half_extents.x;
	const Vector3 y = vector3(tm.y.x, tm.y.y, tm.y.z) * half_extents.y;
	const Vector3 z = vector3(tm.z.x, tm.z.y, tm.z.z) * half_extents.z;

	// Back face
	add_line(o - x - y - z, o + x - y - z, color);
	add_line(o + x - y - z, o + x + y - z, color);
	add_line(o + x + y - z, o - x + y - z, color);
	add_line(o - x + y - z, o - x - y - z, color);

	add_line(o - x - y + z, o + x - y + z, color);
	add_line(o + x - y + z, o + x + y + z, color);
	add_line(o + x + y + z, o - x + y + z, color);
	add_line(o - x + y + z, o - x - y + z, color);

	add_line(o - x - y - z, o - x - y + z, color);
	add_line(o + x - y - z, o + x - y + z, color);
	add_line(o + x + y - z, o + x + y + z, color);
	add_line(o - x + y - z, o - x + y + z, color);
}

void DebugLine::reset()
{
	_num = 0;
}

void DebugLine::submit()
{
	if (!_num)
		return;

	if (!checkAvailTransientVertexBuffer(_num * 2, _vertex_decl))
		return;

	bgfx::TransientVertexBuffer tvb;
	bgfx::allocTransientVertexBuffer(&tvb, _num * 2, _vertex_decl);
	memcpy(tvb.data, _lines, sizeof(Line) * _num);

	const ShaderManager::ShaderData& sd = device()->shader_manager()->get(_shader);

	bgfx::setVertexBuffer(&tvb, 0, _num * 2);
	bgfx::setState(sd.state);
	bgfx::submit(0, sd.program);
}

} // namespace crown
<commit_msg>Fix merge<commit_after>/*
 * Copyright (c) 2012-2016 Daniele Bartolini and individual contributors.
 * License: https://github.com/taylor001/crown/blob/master/LICENSE
 */

#include "debug_line.h"
#include "math_utils.h"
#include "color4.h"
#include "vector3.h"
#include "matrix4x4.h"
#include "error.h"
#include "device.h"
#include <string.h> // memcpy

namespace crown
{

DebugLine::DebugLine(bool depth_test)
	: _marker(MARKER)
	, _shader(depth_test ? "debug_line" : "debug_line_noz")
	, _num(0)
{
	_vertex_decl.begin()
		.add(bgfx::Attrib::Position, 3, bgfx::AttribType::Float)
		.add(bgfx::Attrib::Color0,   4, bgfx::AttribType::Uint8, true)
		.end();
}

DebugLine::~DebugLine()
{
	_marker = 0;
}

void DebugLine::add_line(const Vector3& start, const Vector3& end, const Color4& color)
{
	if (_num >= CROWN_MAX_DEBUG_LINES)
		 return;

	_lines[_num].p0 = start;
	_lines[_num].c0 = to_abgr(color);
	_lines[_num].p1 = end;
	_lines[_num].c1 = to_abgr(color);

	++_num;
}

void DebugLine::add_axes(const Matrix4x4& m, float length)
{
	const Vector3 pos = translation(m);
	add_line(pos, pos + x(m)*length, COLOR4_RED);
	add_line(pos, pos + y(m)*length, COLOR4_GREEN);
	add_line(pos, pos + z(m)*length, COLOR4_BLUE);
}

void DebugLine::add_circle(const Vector3& center, float radius, const Vector3& normal, const Color4& color, uint32_t segments)
{
	const Vector3 dir = normal;
	const Vector3 arr[] =
	{
		{ dir.z, dir.z, -dir.x -dir.y },
		{ -dir.y -dir.z, dir.x, dir.x }
	};
	const int idx = ((dir.z != 0.0f) && (-dir.x != dir.y));
	Vector3 right = arr[idx];
	normalize(right);

	const float incr = 360.0f / (float)(segments >= 3 ? segments : 3);
	float deg0 = 0.0f;
	for (uint32_t ss = 0; ss < segments; ++ss, deg0 += incr)
	{
		const float rad0 = to_rad(deg0);
		const float rad1 = to_rad(deg0 + incr);

		const Vector3 from0 = right*cos(-rad0) + cross(dir, right)*sin(-rad0) + dir*dot(dir, right)*(1.0f-cos(-rad0));
		const Vector3 from1 = right*cos(-rad1) + cross(dir, right)*sin(-rad1) + dir*dot(dir, right)*(1.0f-cos(-rad1));

		add_line(center + radius*from0, center + radius*from1, color);
	}
}

void DebugLine::add_cone(const Vector3& from, const Vector3& to, float radius, const Color4& color, uint32_t segments)
{
	Vector3 dir = to - from;
	normalize(dir);
	const Vector3 arr[] =
	{
		{ dir.z, dir.z, -dir.x -dir.y },
		{ -dir.y -dir.z, dir.x, dir.x }
	};
	const int idx = ((dir.z != 0.0f) && (-dir.x != dir.y));
	Vector3 right = arr[idx];
	normalize(right);

	const float incr = 360.0f / (float)(segments >= 3 ? segments : 3);
	float deg0 = 0.0f;
	for (uint32_t ss = 0; ss < segments; ++ss, deg0 += incr)
	{
		const float rad0 = to_rad(deg0);
		const float rad1 = to_rad(deg0 + incr);

		const Vector3 from0 = right*cos(-rad0) + cross(dir, right)*sin(-rad0) + dir*dot(dir, right)*(1.0f-cos(-rad0));
		const Vector3 from1 = right*cos(-rad1) + cross(dir, right)*sin(-rad1) + dir*dot(dir, right)*(1.0f-cos(-rad1));

		add_line(from + radius*from0, to, color);
		add_line(from + radius*from0, from + radius*from1, color);
	}
}

void DebugLine::add_sphere(const Vector3& center, const float radius, const Color4& color, uint32_t segments)
{
	add_circle(center, radius, VECTOR3_XAXIS, color, segments);
	add_circle(center, radius, VECTOR3_YAXIS, color, segments);
	add_circle(center, radius, VECTOR3_ZAXIS, color, segments);
}

void DebugLine::add_obb(const Matrix4x4& tm, const Vector3& half_extents, const Color4& color)
{
	const Vector3 o = vector3(tm.t.x, tm.t.y, tm.t.z);
	const Vector3 x = vector3(tm.x.x, tm.x.y, tm.x.z) * half_extents.x;
	const Vector3 y = vector3(tm.y.x, tm.y.y, tm.y.z) * half_extents.y;
	const Vector3 z = vector3(tm.z.x, tm.z.y, tm.z.z) * half_extents.z;

	// Back face
	add_line(o - x - y - z, o + x - y - z, color);
	add_line(o + x - y - z, o + x + y - z, color);
	add_line(o + x + y - z, o - x + y - z, color);
	add_line(o - x + y - z, o - x - y - z, color);

	add_line(o - x - y + z, o + x - y + z, color);
	add_line(o + x - y + z, o + x + y + z, color);
	add_line(o + x + y + z, o - x + y + z, color);
	add_line(o - x + y + z, o - x - y + z, color);

	add_line(o - x - y - z, o - x - y + z, color);
	add_line(o + x - y - z, o + x - y + z, color);
	add_line(o + x + y - z, o + x + y + z, color);
	add_line(o - x + y - z, o - x + y + z, color);
}

void DebugLine::reset()
{
	_num = 0;
}

void DebugLine::submit()
{
	if (!_num)
		return;

	if (!checkAvailTransientVertexBuffer(_num * 2, _vertex_decl))
		return;

	bgfx::TransientVertexBuffer tvb;
	bgfx::allocTransientVertexBuffer(&tvb, _num * 2, _vertex_decl);
	memcpy(tvb.data, _lines, sizeof(Line) * _num);

	const ShaderManager::ShaderData& sd = device()->shader_manager()->get(_shader);

	bgfx::setVertexBuffer(&tvb, 0, _num * 2);
	bgfx::setState(sd.state);
	bgfx::submit(0, sd.program);
}

} // namespace crown
<|endoftext|>


<commit_before>#include "vwebview.h"

#include <QMenu>
#include <QPoint>
#include <QContextMenuEvent>
#include <QWebEnginePage>
#include <QAction>
#include <QList>
#include <QClipboard>
#include <QMimeData>
#include <QApplication>
#include <QImage>
#include <QFileInfo>
#include "vfile.h"
#include "utils/vclipboardutils.h"
#include "utils/viconutils.h"
#include "vconfigmanager.h"
#include "utils/vwebutils.h"

extern VConfigManager *g_config;

extern VWebUtils *g_webUtils;

// We set the property of the clipboard to mark that the URL copied in the
// clipboard has been altered.
static const QString c_ClipboardPropertyMark = "CopiedImageURLAltered";

VWebView::VWebView(VFile *p_file, QWidget *p_parent)
    : QWebEngineView(p_parent),
      m_file(p_file),
      m_copyImageUrlActionHooked(false),
      m_afterCopyImage(false)
{
    setAcceptDrops(false);

    connect(QApplication::clipboard(), &QClipboard::changed,
            this, &VWebView::handleClipboardChanged);
}

void VWebView::contextMenuEvent(QContextMenuEvent *p_event)
{
    QMenu *menu = page()->createStandardContextMenu();
    menu->setToolTipsVisible(true);

    const QList<QAction *> actions = menu->actions();

#if defined(Q_OS_WIN)
    if (!m_copyImageUrlActionHooked) {
        // "Copy Image URL" action will put the encoded URL to the clipboard as text
        // and the URL as URLs. If the URL contains Chinese, OneNote or Word could not
        // recognize it.
        // We need to change it to only-space-encoded text.
        QAction *copyImageUrlAct = pageAction(QWebEnginePage::CopyImageUrlToClipboard);
        if (actions.contains(copyImageUrlAct)) {
            connect(copyImageUrlAct, &QAction::triggered,
                    this, &VWebView::handleCopyImageUrlAction);

            m_copyImageUrlActionHooked = true;

            qDebug() << "hooked CopyImageUrl action" << copyImageUrlAct;
        }
    }
#endif

    if (!hasSelection() && m_file && m_file->isModifiable()) {
        QAction *editAct= new QAction(VIconUtils::menuIcon(":/resources/icons/edit_note.svg"),
                                      tr("&Edit"), menu);
        editAct->setToolTip(tr("Edit current note"));
        connect(editAct, &QAction::triggered,
                this, &VWebView::handleEditAction);
        menu->insertAction(actions.isEmpty() ? NULL : actions[0], editAct);
        // actions does not contain editAction.
        if (!actions.isEmpty()) {
            menu->insertSeparator(actions[0]);
        }
    }

    // Add Copy As menu.
    QAction *copyAct = pageAction(QWebEnginePage::Copy);
    if (actions.contains(copyAct)) {
        initCopyAsMenu(copyAct, menu);
    }

    // We need to replace the "Copy Image" action:
    // - the default one use the fully-encoded URL to fetch the image while
    // Windows seems to not recognize it.
    // - We need to remove the html to let it be recognized by some web pages.
    QAction *defaultCopyImageAct = pageAction(QWebEnginePage::CopyImageToClipboard);
    if (actions.contains(defaultCopyImageAct)) {
        QAction *copyImageAct = new QAction(defaultCopyImageAct->text(), menu);
        copyImageAct->setToolTip(defaultCopyImageAct->toolTip());
        connect(copyImageAct, &QAction::triggered,
                this, &VWebView::copyImage);
        menu->insertAction(defaultCopyImageAct, copyImageAct);
        defaultCopyImageAct->setVisible(false);
    }

    // Add Copy All As menu.
    initCopyAllAsMenu(menu);

    hideUnusedActions(menu);

    menu->exec(p_event->globalPos());
    delete menu;
}

void VWebView::handleEditAction()
{
    emit editNote();
}

void VWebView::copyImage()
{
#if defined(Q_OS_WIN)
    Q_ASSERT(m_copyImageUrlActionHooked);
    // triggerPageAction(QWebEnginePage::CopyImageUrlToClipboard) will not really
    // trigger the corresponding action. It just do the stuff directly.
    QAction *copyImageUrlAct = pageAction(QWebEnginePage::CopyImageUrlToClipboard);
    copyImageUrlAct->trigger();

    QCoreApplication::processEvents();

    QClipboard *clipboard = QApplication::clipboard();
    if (clipboard->property(c_ClipboardPropertyMark.toLatin1()).toBool()) {
        const QMimeData *mimeData = clipboard->mimeData();
        QString imgPath;
        if (mimeData->hasUrls()) {
            QList<QUrl> urls = mimeData->urls();
            if (!urls.isEmpty() && urls[0].isLocalFile()) {
                imgPath = urls[0].toLocalFile();
            }
        }

        if (!imgPath.isEmpty()) {
            QImage img(imgPath);
            if (!img.isNull()) {
                m_afterCopyImage = false;
                VClipboardUtils::setImageToClipboard(clipboard, img, QClipboard::Clipboard);
                qDebug() << "clipboard copy image via URL" << imgPath;
                return;
            }
        }
    }
#endif

    m_afterCopyImage = true;

    // Fall back.
    triggerPageAction(QWebEnginePage::CopyImageToClipboard);
}

void VWebView::handleCopyImageUrlAction()
{
    // To avoid failure of setting clipboard mime data.
    QCoreApplication::processEvents();

    QClipboard *clipboard = QApplication::clipboard();
    const QMimeData *mimeData = clipboard->mimeData();
    clipboard->setProperty(c_ClipboardPropertyMark.toLatin1(), false);
    if (clipboard->ownsClipboard()
        && mimeData->hasText()
        && mimeData->hasUrls()) {
        QString text = mimeData->text();
        QList<QUrl> urls = mimeData->urls();
        if (urls.size() == 1
            && urls[0].isLocalFile()
            && urls[0].toEncoded() == text) {
            QString spaceOnlyText = urls[0].toString(QUrl::EncodeSpaces);
            if (spaceOnlyText != text) {
                // Set new mime data.
                QMimeData *data = new QMimeData();
                data->setUrls(urls);
                data->setText(spaceOnlyText);
                VClipboardUtils::setMimeDataToClipboard(clipboard, data, QClipboard::Clipboard);

                clipboard->setProperty(c_ClipboardPropertyMark.toLatin1(), true);
                qDebug() << "clipboard copy image URL altered" << spaceOnlyText;
            }
        }
    }
}

void VWebView::hideUnusedActions(QMenu *p_menu)
{
    QList<QAction *> unusedActions;

    // QWebEnginePage uses different actions of Back/Forward/Reload.
    // [Woboq](https://code.woboq.org/qt5/qtwebengine/src/webenginewidgets/api/qwebenginepage.cpp.html#1652)
    // We tell these three actions by name.
   const QStringList actionNames({QWebEnginePage::tr("&Back"),
                                  QWebEnginePage::tr("&Forward"),
                                  QWebEnginePage::tr("&Reload")});

    const QList<QAction *> actions = p_menu->actions();
    for (auto it : actions) {
        if (actionNames.contains(it->text())) {
            unusedActions.append(it);
        }
    }

    // ViewSource.
    QAction *act = pageAction(QWebEnginePage::ViewSource);
    unusedActions.append(act);

    // DownloadImageToDisk.
    act = pageAction(QWebEnginePage::DownloadImageToDisk);
    unusedActions.append(act);

    // DownloadLinkToDisk.
    act = pageAction(QWebEnginePage::DownloadLinkToDisk);
    unusedActions.append(act);

    for (auto it : unusedActions) {
        if (it) {
            it->setVisible(false);
        }
    }
}

bool VWebView::removeStyles(QString &p_html)
{
    bool changed = false;

    const QStringList &styles = g_config->getStylesToRemoveWhenCopied();
    if (styles.isEmpty()) {
        return changed;
    }

    QRegExp tagReg("(<[^>]+\\sstyle=[^>]*>)");

    int pos = 0;
    while (pos < p_html.size()) {
        int idx = p_html.indexOf(tagReg, pos);
        if (idx == -1) {
            break;
        }

        QString styleStr = tagReg.cap(1);
        QString alteredStyleStr = styleStr;

        QString regPatt("(\\s|\")%1:[^;]+;");
        for (auto const & sty : styles) {
            QRegExp reg(regPatt.arg(sty));
            alteredStyleStr.replace(reg, "\\1");
        }

        pos = idx + tagReg.matchedLength();
        if (styleStr != alteredStyleStr) {
            pos = pos + alteredStyleStr.size() - styleStr.size();
            p_html.replace(idx, tagReg.matchedLength(), alteredStyleStr);
            changed = true;
        }
    }

    return changed;
}

void VWebView::handleClipboardChanged(QClipboard::Mode p_mode)
{
    QString copyTarget = m_copyTarget;
    m_copyTarget.clear();

    bool afterCopyImage = m_afterCopyImage;
    m_afterCopyImage = false;

    if (!hasFocus()
        || p_mode != QClipboard::Clipboard) {
        return;
    }

    QClipboard *clipboard = QApplication::clipboard();
    const QMimeData *mimeData = clipboard->mimeData();
    if (!clipboard->ownsClipboard()) {
        return;
    }

    if (afterCopyImage) {
        removeHtmlFromImageData(clipboard, mimeData);
    } else {
        alterHtmlMimeData(clipboard, mimeData, copyTarget);
    }
}

void VWebView::alterHtmlMimeData(QClipboard *p_clipboard,
                                 const QMimeData *p_mimeData,
                                 const QString &p_copyTarget)
{
    if (!p_mimeData->hasHtml()
        || p_mimeData->hasImage()
        || p_copyTarget.isEmpty()) {
        return;
    }

    QString html = p_mimeData->html();
    if (!g_webUtils->alterHtmlAsTarget(url(), html, p_copyTarget)) {
        return;
    }

    // Set new mime data.
    QMimeData *data = VClipboardUtils::cloneMimeData(p_mimeData);
    data->setHtml(html);

    VClipboardUtils::setMimeDataToClipboard(p_clipboard, data, QClipboard::Clipboard);
    qDebug() << "altered clipboard's Html";
}

void VWebView::removeHtmlFromImageData(QClipboard *p_clipboard,
                                       const QMimeData *p_mimeData)
{
    if (!p_mimeData->hasImage()) {
        return;
    }

    if (p_mimeData->hasHtml()) {
        qDebug() << "remove html from image data" << p_mimeData->html();
        QMimeData *data = new QMimeData();
        data->setImageData(p_mimeData->imageData());
        VClipboardUtils::setMimeDataToClipboard(p_clipboard, data, QClipboard::Clipboard);
    }
}

void VWebView::initCopyAsMenu(QAction *p_after, QMenu *p_menu)
{
    QStringList targets = g_webUtils->getCopyTargetsName();
    if (targets.isEmpty()) {
        return;
    }

    QMenu *subMenu = new QMenu(tr("Copy As"), p_menu);
    subMenu->setToolTipsVisible(true);
    for (auto const & target : targets) {
        QAction *act = new QAction(target, subMenu);
        act->setData(target);
        act->setToolTip(tr("Copy selected content using rules specified by target %1").arg(target));

        subMenu->addAction(act);
    }

    connect(subMenu, &QMenu::triggered,
            this, &VWebView::handleCopyAsAction);

    QAction *menuAct = p_menu->insertMenu(p_after, subMenu);
    p_menu->removeAction(p_after);
    p_menu->insertAction(menuAct, p_after);
}

void VWebView::handleCopyAsAction(QAction *p_act)
{
    if (!p_act) {
        return;
    }

    m_copyTarget = p_act->data().toString();

    triggerPageAction(QWebEnginePage::Copy);
}

void VWebView::initCopyAllAsMenu(QMenu *p_menu)
{
    QStringList targets = g_webUtils->getCopyTargetsName();
    if (targets.isEmpty()) {
        return;
    }

    QMenu *subMenu = new QMenu(tr("Copy All As"), p_menu);
    subMenu->setToolTipsVisible(true);
    for (auto const & target : targets) {
        QAction *act = new QAction(target, subMenu);
        act->setData(target);
        act->setToolTip(tr("Copy all content using rules specified by target %1").arg(target));

        subMenu->addAction(act);
    }

    connect(subMenu, &QMenu::triggered,
            this, &VWebView::handleCopyAllAsAction);

    p_menu->addSeparator();
    p_menu->addMenu(subMenu);
}

void VWebView::handleCopyAllAsAction(QAction *p_act)
{
    if (!p_act) {
        return;
    }

    triggerPageAction(QWebEnginePage::SelectAll);

    m_copyTarget = p_act->data().toString();

    triggerPageAction(QWebEnginePage::Copy);

    triggerPageAction(QWebEnginePage::Unselect);
}

<commit_msg>fix Copy All As logics<commit_after>#include "vwebview.h"

#include <QMenu>
#include <QPoint>
#include <QContextMenuEvent>
#include <QWebEnginePage>
#include <QAction>
#include <QList>
#include <QClipboard>
#include <QMimeData>
#include <QApplication>
#include <QImage>
#include <QFileInfo>
#include "vfile.h"
#include "utils/vclipboardutils.h"
#include "utils/viconutils.h"
#include "vconfigmanager.h"
#include "utils/vwebutils.h"

extern VConfigManager *g_config;

extern VWebUtils *g_webUtils;

// We set the property of the clipboard to mark that the URL copied in the
// clipboard has been altered.
static const QString c_ClipboardPropertyMark = "CopiedImageURLAltered";

VWebView::VWebView(VFile *p_file, QWidget *p_parent)
    : QWebEngineView(p_parent),
      m_file(p_file),
      m_copyImageUrlActionHooked(false),
      m_afterCopyImage(false)
{
    setAcceptDrops(false);

    connect(QApplication::clipboard(), &QClipboard::changed,
            this, &VWebView::handleClipboardChanged);
}

void VWebView::contextMenuEvent(QContextMenuEvent *p_event)
{
    QMenu *menu = page()->createStandardContextMenu();
    menu->setToolTipsVisible(true);

    const QList<QAction *> actions = menu->actions();

#if defined(Q_OS_WIN)
    if (!m_copyImageUrlActionHooked) {
        // "Copy Image URL" action will put the encoded URL to the clipboard as text
        // and the URL as URLs. If the URL contains Chinese, OneNote or Word could not
        // recognize it.
        // We need to change it to only-space-encoded text.
        QAction *copyImageUrlAct = pageAction(QWebEnginePage::CopyImageUrlToClipboard);
        if (actions.contains(copyImageUrlAct)) {
            connect(copyImageUrlAct, &QAction::triggered,
                    this, &VWebView::handleCopyImageUrlAction);

            m_copyImageUrlActionHooked = true;

            qDebug() << "hooked CopyImageUrl action" << copyImageUrlAct;
        }
    }
#endif

    if (!hasSelection() && m_file && m_file->isModifiable()) {
        QAction *editAct= new QAction(VIconUtils::menuIcon(":/resources/icons/edit_note.svg"),
                                      tr("&Edit"), menu);
        editAct->setToolTip(tr("Edit current note"));
        connect(editAct, &QAction::triggered,
                this, &VWebView::handleEditAction);
        menu->insertAction(actions.isEmpty() ? NULL : actions[0], editAct);
        // actions does not contain editAction.
        if (!actions.isEmpty()) {
            menu->insertSeparator(actions[0]);
        }
    }

    // Add Copy As menu.
    QAction *copyAct = pageAction(QWebEnginePage::Copy);
    if (actions.contains(copyAct)) {
        initCopyAsMenu(copyAct, menu);
    }

    // We need to replace the "Copy Image" action:
    // - the default one use the fully-encoded URL to fetch the image while
    // Windows seems to not recognize it.
    // - We need to remove the html to let it be recognized by some web pages.
    QAction *defaultCopyImageAct = pageAction(QWebEnginePage::CopyImageToClipboard);
    if (actions.contains(defaultCopyImageAct)) {
        QAction *copyImageAct = new QAction(defaultCopyImageAct->text(), menu);
        copyImageAct->setToolTip(defaultCopyImageAct->toolTip());
        connect(copyImageAct, &QAction::triggered,
                this, &VWebView::copyImage);
        menu->insertAction(defaultCopyImageAct, copyImageAct);
        defaultCopyImageAct->setVisible(false);
    }

    // Add Copy All As menu.
    initCopyAllAsMenu(menu);

    hideUnusedActions(menu);

    menu->exec(p_event->globalPos());
    delete menu;
}

void VWebView::handleEditAction()
{
    emit editNote();
}

void VWebView::copyImage()
{
#if defined(Q_OS_WIN)
    Q_ASSERT(m_copyImageUrlActionHooked);
    // triggerPageAction(QWebEnginePage::CopyImageUrlToClipboard) will not really
    // trigger the corresponding action. It just do the stuff directly.
    QAction *copyImageUrlAct = pageAction(QWebEnginePage::CopyImageUrlToClipboard);
    copyImageUrlAct->trigger();

    QCoreApplication::processEvents();

    QClipboard *clipboard = QApplication::clipboard();
    if (clipboard->property(c_ClipboardPropertyMark.toLatin1()).toBool()) {
        const QMimeData *mimeData = clipboard->mimeData();
        QString imgPath;
        if (mimeData->hasUrls()) {
            QList<QUrl> urls = mimeData->urls();
            if (!urls.isEmpty() && urls[0].isLocalFile()) {
                imgPath = urls[0].toLocalFile();
            }
        }

        if (!imgPath.isEmpty()) {
            QImage img(imgPath);
            if (!img.isNull()) {
                m_afterCopyImage = false;
                VClipboardUtils::setImageToClipboard(clipboard, img, QClipboard::Clipboard);
                qDebug() << "clipboard copy image via URL" << imgPath;
                return;
            }
        }
    }
#endif

    m_afterCopyImage = true;

    // Fall back.
    triggerPageAction(QWebEnginePage::CopyImageToClipboard);
}

void VWebView::handleCopyImageUrlAction()
{
    // To avoid failure of setting clipboard mime data.
    QCoreApplication::processEvents();

    QClipboard *clipboard = QApplication::clipboard();
    const QMimeData *mimeData = clipboard->mimeData();
    clipboard->setProperty(c_ClipboardPropertyMark.toLatin1(), false);
    if (clipboard->ownsClipboard()
        && mimeData->hasText()
        && mimeData->hasUrls()) {
        QString text = mimeData->text();
        QList<QUrl> urls = mimeData->urls();
        if (urls.size() == 1
            && urls[0].isLocalFile()
            && urls[0].toEncoded() == text) {
            QString spaceOnlyText = urls[0].toString(QUrl::EncodeSpaces);
            if (spaceOnlyText != text) {
                // Set new mime data.
                QMimeData *data = new QMimeData();
                data->setUrls(urls);
                data->setText(spaceOnlyText);
                VClipboardUtils::setMimeDataToClipboard(clipboard, data, QClipboard::Clipboard);

                clipboard->setProperty(c_ClipboardPropertyMark.toLatin1(), true);
                qDebug() << "clipboard copy image URL altered" << spaceOnlyText;
            }
        }
    }
}

void VWebView::hideUnusedActions(QMenu *p_menu)
{
    QList<QAction *> unusedActions;

    // QWebEnginePage uses different actions of Back/Forward/Reload.
    // [Woboq](https://code.woboq.org/qt5/qtwebengine/src/webenginewidgets/api/qwebenginepage.cpp.html#1652)
    // We tell these three actions by name.
   const QStringList actionNames({QWebEnginePage::tr("&Back"),
                                  QWebEnginePage::tr("&Forward"),
                                  QWebEnginePage::tr("&Reload")});

    const QList<QAction *> actions = p_menu->actions();
    for (auto it : actions) {
        if (actionNames.contains(it->text())) {
            unusedActions.append(it);
        }
    }

    // ViewSource.
    QAction *act = pageAction(QWebEnginePage::ViewSource);
    unusedActions.append(act);

    // DownloadImageToDisk.
    act = pageAction(QWebEnginePage::DownloadImageToDisk);
    unusedActions.append(act);

    // DownloadLinkToDisk.
    act = pageAction(QWebEnginePage::DownloadLinkToDisk);
    unusedActions.append(act);

    for (auto it : unusedActions) {
        if (it) {
            it->setVisible(false);
        }
    }
}

bool VWebView::removeStyles(QString &p_html)
{
    bool changed = false;

    const QStringList &styles = g_config->getStylesToRemoveWhenCopied();
    if (styles.isEmpty()) {
        return changed;
    }

    QRegExp tagReg("(<[^>]+\\sstyle=[^>]*>)");

    int pos = 0;
    while (pos < p_html.size()) {
        int idx = p_html.indexOf(tagReg, pos);
        if (idx == -1) {
            break;
        }

        QString styleStr = tagReg.cap(1);
        QString alteredStyleStr = styleStr;

        QString regPatt("(\\s|\")%1:[^;]+;");
        for (auto const & sty : styles) {
            QRegExp reg(regPatt.arg(sty));
            alteredStyleStr.replace(reg, "\\1");
        }

        pos = idx + tagReg.matchedLength();
        if (styleStr != alteredStyleStr) {
            pos = pos + alteredStyleStr.size() - styleStr.size();
            p_html.replace(idx, tagReg.matchedLength(), alteredStyleStr);
            changed = true;
        }
    }

    return changed;
}

void VWebView::handleClipboardChanged(QClipboard::Mode p_mode)
{
    if (!hasFocus()
        || p_mode != QClipboard::Clipboard) {
        return;
    }

    QClipboard *clipboard = QApplication::clipboard();
    if (!clipboard->ownsClipboard()) {
        return;
    }

    const QMimeData *mimeData = clipboard->mimeData();

    QString copyTarget = m_copyTarget;
    m_copyTarget.clear();

    if (m_afterCopyImage) {
        m_afterCopyImage = false;
        removeHtmlFromImageData(clipboard, mimeData);
    } else {
        alterHtmlMimeData(clipboard, mimeData, copyTarget);
    }
}

void VWebView::alterHtmlMimeData(QClipboard *p_clipboard,
                                 const QMimeData *p_mimeData,
                                 const QString &p_copyTarget)
{
    if (!p_mimeData->hasHtml()
        || p_mimeData->hasImage()
        || p_copyTarget.isEmpty()) {
        return;
    }

    QString html = p_mimeData->html();
    if (!g_webUtils->alterHtmlAsTarget(url(), html, p_copyTarget)) {
        return;
    }

    // Set new mime data.
    QMimeData *data = VClipboardUtils::cloneMimeData(p_mimeData);
    data->setHtml(html);

    VClipboardUtils::setMimeDataToClipboard(p_clipboard, data, QClipboard::Clipboard);
    qDebug() << "altered clipboard's Html";
}

void VWebView::removeHtmlFromImageData(QClipboard *p_clipboard,
                                       const QMimeData *p_mimeData)
{
    if (!p_mimeData->hasImage()) {
        return;
    }

    if (p_mimeData->hasHtml()) {
        qDebug() << "remove html from image data" << p_mimeData->html();
        QMimeData *data = new QMimeData();
        data->setImageData(p_mimeData->imageData());
        VClipboardUtils::setMimeDataToClipboard(p_clipboard, data, QClipboard::Clipboard);
    }
}

void VWebView::initCopyAsMenu(QAction *p_after, QMenu *p_menu)
{
    QStringList targets = g_webUtils->getCopyTargetsName();
    if (targets.isEmpty()) {
        return;
    }

    QMenu *subMenu = new QMenu(tr("Copy As"), p_menu);
    subMenu->setToolTipsVisible(true);
    for (auto const & target : targets) {
        QAction *act = new QAction(target, subMenu);
        act->setData(target);
        act->setToolTip(tr("Copy selected content using rules specified by target %1").arg(target));

        subMenu->addAction(act);
    }

    connect(subMenu, &QMenu::triggered,
            this, &VWebView::handleCopyAsAction);

    QAction *menuAct = p_menu->insertMenu(p_after, subMenu);
    p_menu->removeAction(p_after);
    p_menu->insertAction(menuAct, p_after);
}

void VWebView::handleCopyAsAction(QAction *p_act)
{
    if (!p_act) {
        return;
    }

    m_copyTarget = p_act->data().toString();

    triggerPageAction(QWebEnginePage::Copy);
}

void VWebView::initCopyAllAsMenu(QMenu *p_menu)
{
    QStringList targets = g_webUtils->getCopyTargetsName();
    if (targets.isEmpty()) {
        return;
    }

    QMenu *subMenu = new QMenu(tr("Copy All As"), p_menu);
    subMenu->setToolTipsVisible(true);
    for (auto const & target : targets) {
        QAction *act = new QAction(target, subMenu);
        act->setData(target);
        act->setToolTip(tr("Copy all content using rules specified by target %1").arg(target));

        subMenu->addAction(act);
    }

    connect(subMenu, &QMenu::triggered,
            this, &VWebView::handleCopyAllAsAction);

    p_menu->addSeparator();
    p_menu->addMenu(subMenu);
}

void VWebView::handleCopyAllAsAction(QAction *p_act)
{
    if (!p_act) {
        return;
    }

    triggerPageAction(QWebEnginePage::SelectAll);

    m_copyTarget = p_act->data().toString();

    triggerPageAction(QWebEnginePage::Copy);

    triggerPageAction(QWebEnginePage::Unselect);
}

<|endoftext|>


<commit_before>#include <ResourceManager.h>
ResourceManager::ResourceManager()
{

}

ResourceManager::~ResourceManager()
{

}

bool ResourceManager::Init(string root)
{
	res_root = root;
	return file.Init();

}

void ResourceManager::Update(Uint32 delta)
{
	file.Update(delta);
}

bool ResourceManager::Cache(string filename, ResPtr res)
{
	return true;
}

bool ResourceManager::DeCache(string filename)
{
	return true;
}

bool ResourceManager::GetRes(string name, std::function<void(ResPtr) > cb)
{
	auto itr = cache.find(name);
	if (itr!= cache.end() && !itr->second.expired() )
	{
		ResPtr ptr = itr->second.lock();
		cb(ptr);
	}
	else
	{
		file.OpenFile(res_root + name, [=](FileWork * work) {
			ResItem * item = new ResItem;
			item->buff = work->buff;
			item->size = work->size;
			item->filename = name;

			ResPtr ptr(item, [=](ResItem * res) {
				cout << "Res decache:" << res->filename << endl;
				this->DeCache(res->filename);
				delete res;
			});

			cb(ptr);
			this->cache.insert({ name, ptr });
		});
	}

	return true;
}

bool ResourceManager::GetBigRes(string name, std::function<void(ResPtr) > cb)
{
	return true;
}

bool ResourceManager::SaveRes(string name, ResPtr res)
{
	return true;
}

<commit_msg>make_shared 不支持删除器 无法优化内存分配<commit_after>#include <ResourceManager.h>
ResourceManager::ResourceManager()
{

}

ResourceManager::~ResourceManager()
{

}

bool ResourceManager::Init(string root)
{
	res_root = root;
	return file.Init();

}

void ResourceManager::Update(Uint32 delta)
{
	file.Update(delta);
}

bool ResourceManager::Cache(string filename, ResPtr res)
{
	return true;
}

bool ResourceManager::DeCache(string filename)
{
	return true;
}

bool ResourceManager::GetRes(string name, std::function<void(ResPtr) > cb)
{
	auto itr = cache.find(name);
	if (itr!= cache.end() && !itr->second.expired() )
	{
		ResPtr ptr = itr->second.lock();
		cb(ptr);
	}
	else
	{
		file.OpenFile(res_root + name, [=](FileWork * work) {
			ResItem * item = new ResItem;
			item->buff = work->buff;
			item->size = work->size;
			item->filename = name;

			ResPtr ptr(item, [=](ResItem * res) {
				cout << "Res decache:" << res->filename << endl;
				this->DeCache(res->filename);
				delete res;
			});//make_shared ֧ɾ ޷Żڴ

			cb(ptr);
			this->cache.insert({ name, ptr });
		});
	}

	return true;
}

bool ResourceManager::GetBigRes(string name, std::function<void(ResPtr) > cb)
{
	return true;
}

bool ResourceManager::SaveRes(string name, ResPtr res)
{
	return true;
}

<|endoftext|>