<commit_before>// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.

#pragma once

#if !defined(RXCPP_RX_SUBSCRIPTION_HPP)
#define RXCPP_RX_SUBSCRIPTION_HPP

#include "rx-includes.hpp"

namespace rxcpp {

namespace detail {

template<class F>
struct is_unsubscribe_function
{
    struct not_void {};
    template<class CF>
    static auto check(int) -> decltype((*(CF*)nullptr)());
    template<class CF>
    static not_void check(...);

    static const bool value = std::is_same<decltype(check<typename std::decay<F>::type>(0)), void>::value;
};

}

struct tag_subscription {};
struct subscription_base {typedef tag_subscription subscription_tag;};
template<class T>
class is_subscription
{
    template<class C>
    static typename C::subscription_tag* check(int);
    template<class C>
    static void check(...);
public:
    static const bool value = std::is_convertible<decltype(check<typename std::decay<T>::type>(0)), tag_subscription*>::value;
};

template<class Unsubscribe>
class static_subscription
{
    typedef typename std::decay<Unsubscribe>::type unsubscribe_call_type;
    unsubscribe_call_type unsubscribe_call;
    static_subscription()
    {
    }
public:
    static_subscription(const static_subscription& o)
        : unsubscribe_call(o.unsubscribe_call)
    {
    }
    static_subscription(static_subscription&& o)
        : unsubscribe_call(std::move(o.unsubscribe_call))
    {
    }
    static_subscription(unsubscribe_call_type s)
        : unsubscribe_call(std::move(s))
    {
    }
    void unsubscribe() const {
        unsubscribe_call();
    }
};

class subscription : public subscription_base
{
    class base_subscription_state : public std::enable_shared_from_this<base_subscription_state>
    {
        base_subscription_state();
    public:

        explicit base_subscription_state(bool initial)
            : issubscribed(initial)
        {
        }
        virtual void unsubscribe() {
        }
        std::atomic<bool> issubscribed;
    };
public:
    typedef std::weak_ptr<base_subscription_state> weak_state_type;

private:
    template<class I>
    struct subscription_state : public base_subscription_state
    {
        typedef typename std::decay<I>::type inner_t;
        subscription_state(inner_t i)
            : base_subscription_state(true)
            , inner(std::move(i))
        {
        }
        virtual void unsubscribe() {
            if (issubscribed.exchange(false)) {
                trace_activity().unsubscribe_enter(*this);
                inner.unsubscribe();
                trace_activity().unsubscribe_return(*this);
            }
        }
        inner_t inner;
    };

protected:
    std::shared_ptr<base_subscription_state> state;

    friend bool operator<(const subscription&, const subscription&);
    friend bool operator==(const subscription&, const subscription&);

private:
    subscription(weak_state_type w)
        : state(w.lock())
    {
        if (!state) {
            abort();
        }
    }
public:

    subscription()
        : state(std::make_shared<base_subscription_state>(false))
    {
        if (!state) {
            abort();
        }
    }
    template<class U>
    explicit subscription(U u, typename std::enable_if<!is_subscription<U>::value, void**>::type = nullptr)
        : state(std::make_shared<subscription_state<U>>(std::move(u)))
    {
        if (!state) {
            abort();
        }
    }
    template<class U>
    explicit subscription(U u, typename std::enable_if<!std::is_same<subscription, U>::value && is_subscription<U>::value, void**>::type = nullptr)
        // intentionally slice
        : state(std::move((*static_cast<subscription*>(&u)).state))
    {
        if (!state) {
            abort();
        }
    }
    subscription(const subscription& o)
        : state(o.state)
    {
        if (!state) {
            abort();
        }
    }
    subscription(subscription&& o)
        : state(std::move(o.state))
    {
        if (!state) {
            abort();
        }
    }
    subscription& operator=(subscription o) {
        state = std::move(o.state);
        return *this;
    }
    bool is_subscribed() const {
        if (!state) {
            abort();
        }
        return state->issubscribed;
    }
    void unsubscribe() const {
        if (!state) {
            abort();
        }
        auto keepAlive = state;
        state->unsubscribe();
    }

    weak_state_type get_weak() {
        return state;
    }
    static subscription lock(weak_state_type w) {
        return subscription(w);
    }
};

inline bool operator<(const subscription& lhs, const subscription& rhs) {
    return lhs.state < rhs.state;
}
inline bool operator==(const subscription& lhs, const subscription& rhs) {
    return lhs.state == rhs.state;
}
inline bool operator!=(const subscription& lhs, const subscription& rhs) {
    return !(lhs == rhs);
}

inline auto make_subscription()
    ->      subscription {
    return  subscription();
}
template<class I>
auto make_subscription(I&& i)
    -> typename std::enable_if<!is_subscription<I>::value && !detail::is_unsubscribe_function<I>::value,
            subscription>::type {
    return  subscription(std::forward<I>(i));
}
template<class Unsubscribe>
auto make_subscription(Unsubscribe&& u)
    -> typename std::enable_if<detail::is_unsubscribe_function<Unsubscribe>::value,
            subscription>::type {
    return  subscription(static_subscription<Unsubscribe>(std::forward<Unsubscribe>(u)));
}

namespace detail {

struct tag_composite_subscription_empty {};

class composite_subscription_inner
{
private:
    typedef subscription::weak_state_type weak_subscription;
    struct composite_subscription_state : public std::enable_shared_from_this<composite_subscription_state>
    {
        std::set<subscription> subscriptions;
        std::mutex lock;
        std::atomic<bool> issubscribed;

        ~composite_subscription_state()
        {
            std::unique_lock<decltype(lock)> guard(lock);
            subscriptions.clear();
        }

        composite_subscription_state()
            : issubscribed(true)
        {
        }
        composite_subscription_state(tag_composite_subscription_empty)
            : issubscribed(false)
        {
        }

        inline weak_subscription add(subscription s) {
            if (!issubscribed) {
                s.unsubscribe();
            } else if (s.is_subscribed()) {
                std::unique_lock<decltype(lock)> guard(lock);
                subscriptions.insert(s);
            }
            return s.get_weak();
        }

        inline void remove(weak_subscription w) {
            if (issubscribed && !w.expired()) {
                auto s = subscription::lock(w);
                std::unique_lock<decltype(lock)> guard(lock);
                subscriptions.erase(std::move(s));
            }
        }

        inline void clear() {
            if (issubscribed) {
                std::unique_lock<decltype(lock)> guard(lock);

                std::set<subscription> v(std::move(subscriptions));
                guard.unlock();
                std::for_each(v.begin(), v.end(),
                              [](const subscription& s) {
                                s.unsubscribe(); });
            }
        }

        inline void unsubscribe() {
            if (issubscribed.exchange(false)) {
                std::unique_lock<decltype(lock)> guard(lock);

                std::set<subscription> v(std::move(subscriptions));
                guard.unlock();
                std::for_each(v.begin(), v.end(),
                              [](const subscription& s) {
                                s.unsubscribe(); });
            }
        }
    };

public:
    typedef std::shared_ptr<composite_subscription_state> shared_state_type;

protected:
    mutable shared_state_type state;

public:
    composite_subscription_inner()
        : state(std::make_shared<composite_subscription_state>())
    {
    }
    composite_subscription_inner(tag_composite_subscription_empty et)
        : state(std::make_shared<composite_subscription_state>(et))
    {
    }

    composite_subscription_inner(const composite_subscription_inner& o)
        : state(o.state)
    {
        if (!state) {
            abort();
        }
    }
    composite_subscription_inner(composite_subscription_inner&& o)
        : state(std::move(o.state))
    {
        if (!state) {
            abort();
        }
    }

    composite_subscription_inner& operator=(composite_subscription_inner o)
    {
        state = std::move(o.state);
        if (!state) {
            abort();
        }
        return *this;
    }

    inline weak_subscription add(subscription s) const {
        if (!state) {
            abort();
        }
        return state->add(std::move(s));
    }
    inline void remove(weak_subscription w) const {
        if (!state) {
            abort();
        }
        state->remove(std::move(w));
    }
    inline void clear() const {
        if (!state) {
            abort();
        }
        state->clear();
    }
    inline void unsubscribe() {
        if (!state) {
            abort();
        }
        state->unsubscribe();
    }
};

}

class composite_subscription
    : protected detail::composite_subscription_inner
    , public subscription
{
    typedef detail::composite_subscription_inner inner_type;
public:
    typedef subscription::weak_state_type weak_subscription;

    static composite_subscription shared_empty;

    composite_subscription(detail::tag_composite_subscription_empty et)
        : inner_type(et)
        , subscription() // use empty base
    {
    }

public:

    composite_subscription()
        : inner_type()
        , subscription(*static_cast<const inner_type* const>(this))
    {
    }

    composite_subscription(const composite_subscription& o)
        : inner_type(o)
        , subscription(static_cast<const subscription&>(o))
    {
    }
    composite_subscription(composite_subscription&& o)
        : inner_type(std::move(o))
        , subscription(std::move(static_cast<subscription&>(o)))
    {
    }

    composite_subscription& operator=(composite_subscription o)
    {
        inner_type::operator=(std::move(o));
        subscription::operator=(std::move(*static_cast<subscription*>(&o)));
        return *this;
    }

    static inline composite_subscription empty() {
        return shared_empty;
    }

    using subscription::is_subscribed;
    using subscription::unsubscribe;

    using inner_type::clear;

    inline weak_subscription add(subscription s) const {
        if (s == static_cast<const subscription&>(*this)) {
            // do not nest the same subscription
            abort();
            //return s.get_weak();
        }
        auto that = this->subscription::state.get();
        trace_activity().subscription_add_enter(*that, s);
        auto w = inner_type::add(std::move(s));
        trace_activity().subscription_add_return(*that);
        return w;
    }

    template<class F>
    auto add(F f) const
    -> typename std::enable_if<detail::is_unsubscribe_function<F>::value, weak_subscription>::type {
        return add(make_subscription(std::move(f)));
    }

    inline void remove(weak_subscription w) const {
        auto that = this->subscription::state.get();
        trace_activity().subscription_remove_enter(*that, w);
        inner_type::remove(w);
        trace_activity().subscription_remove_return(*that);
    }
};

inline bool operator<(const composite_subscription& lhs, const composite_subscription& rhs) {
    return static_cast<const subscription&>(lhs) < static_cast<const subscription&>(rhs);
}
inline bool operator==(const composite_subscription& lhs, const composite_subscription& rhs) {
    return static_cast<const subscription&>(lhs) == static_cast<const subscription&>(rhs);
}
inline bool operator!=(const composite_subscription& lhs, const composite_subscription& rhs) {
    return !(lhs == rhs);
}

//static
RXCPP_SELECT_ANY composite_subscription composite_subscription::shared_empty = composite_subscription(detail::tag_composite_subscription_empty());

template<class T>
class resource : public subscription_base
{
public:
    typedef typename composite_subscription::weak_subscription weak_subscription;

    resource(T t = T(), composite_subscription cs = composite_subscription())
        : lifetime(std::move(cs))
        , value(std::make_shared<std::unique_ptr<T>>(std::unique_ptr<T>(new T(std::move(t)))))
    {
        auto localValue = value;
        lifetime.add(
            [localValue](){
                localValue->reset();
            }
        );
    }

    T& get() {
        return *value.get()->get();
    }
    composite_subscription& get_subscription() {
        return lifetime;
    }

    bool is_subscribed() const {
        return lifetime.is_subscribed();
    }
    weak_subscription add(subscription s) const {
        return lifetime.add(std::move(s));
    }
    template<class F>
    auto add(F f) const
    -> typename std::enable_if<detail::is_unsubscribe_function<F>::value, weak_subscription>::type {
        return lifetime.add(make_subscription(std::move(f)));
    }
    void remove(weak_subscription w) const {
        return lifetime.remove(std::move(w));
    }
    void clear() const {
        return lifetime.clear();
    }
    void unsubscribe() const {
        return lifetime.unsubscribe();
    }

protected:
    composite_subscription lifetime;
    std::shared_ptr<std::unique_ptr<T>> value;
};

}

#endif
<commit_msg>Small changes on resource class<commit_after>// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.

#pragma once

#if !defined(RXCPP_RX_SUBSCRIPTION_HPP)
#define RXCPP_RX_SUBSCRIPTION_HPP

#include "rx-includes.hpp"

namespace rxcpp {

namespace detail {

template<class F>
struct is_unsubscribe_function
{
    struct not_void {};
    template<class CF>
    static auto check(int) -> decltype((*(CF*)nullptr)());
    template<class CF>
    static not_void check(...);

    static const bool value = std::is_same<decltype(check<typename std::decay<F>::type>(0)), void>::value;
};

}

struct tag_subscription {};
struct subscription_base {typedef tag_subscription subscription_tag;};
template<class T>
class is_subscription
{
    template<class C>
    static typename C::subscription_tag* check(int);
    template<class C>
    static void check(...);
public:
    static const bool value = std::is_convertible<decltype(check<typename std::decay<T>::type>(0)), tag_subscription*>::value;
};

template<class Unsubscribe>
class static_subscription
{
    typedef typename std::decay<Unsubscribe>::type unsubscribe_call_type;
    unsubscribe_call_type unsubscribe_call;
    static_subscription()
    {
    }
public:
    static_subscription(const static_subscription& o)
        : unsubscribe_call(o.unsubscribe_call)
    {
    }
    static_subscription(static_subscription&& o)
        : unsubscribe_call(std::move(o.unsubscribe_call))
    {
    }
    static_subscription(unsubscribe_call_type s)
        : unsubscribe_call(std::move(s))
    {
    }
    void unsubscribe() const {
        unsubscribe_call();
    }
};

class subscription : public subscription_base
{
    class base_subscription_state : public std::enable_shared_from_this<base_subscription_state>
    {
        base_subscription_state();
    public:

        explicit base_subscription_state(bool initial)
            : issubscribed(initial)
        {
        }
        virtual void unsubscribe() {
        }
        std::atomic<bool> issubscribed;
    };
public:
    typedef std::weak_ptr<base_subscription_state> weak_state_type;

private:
    template<class I>
    struct subscription_state : public base_subscription_state
    {
        typedef typename std::decay<I>::type inner_t;
        subscription_state(inner_t i)
            : base_subscription_state(true)
            , inner(std::move(i))
        {
        }
        virtual void unsubscribe() {
            if (issubscribed.exchange(false)) {
                trace_activity().unsubscribe_enter(*this);
                inner.unsubscribe();
                trace_activity().unsubscribe_return(*this);
            }
        }
        inner_t inner;
    };

protected:
    std::shared_ptr<base_subscription_state> state;

    friend bool operator<(const subscription&, const subscription&);
    friend bool operator==(const subscription&, const subscription&);

private:
    subscription(weak_state_type w)
        : state(w.lock())
    {
        if (!state) {
            abort();
        }
    }
public:

    subscription()
        : state(std::make_shared<base_subscription_state>(false))
    {
        if (!state) {
            abort();
        }
    }
    template<class U>
    explicit subscription(U u, typename std::enable_if<!is_subscription<U>::value, void**>::type = nullptr)
        : state(std::make_shared<subscription_state<U>>(std::move(u)))
    {
        if (!state) {
            abort();
        }
    }
    template<class U>
    explicit subscription(U u, typename std::enable_if<!std::is_same<subscription, U>::value && is_subscription<U>::value, void**>::type = nullptr)
        // intentionally slice
        : state(std::move((*static_cast<subscription*>(&u)).state))
    {
        if (!state) {
            abort();
        }
    }
    subscription(const subscription& o)
        : state(o.state)
    {
        if (!state) {
            abort();
        }
    }
    subscription(subscription&& o)
        : state(std::move(o.state))
    {
        if (!state) {
            abort();
        }
    }
    subscription& operator=(subscription o) {
        state = std::move(o.state);
        return *this;
    }
    bool is_subscribed() const {
        if (!state) {
            abort();
        }
        return state->issubscribed;
    }
    void unsubscribe() const {
        if (!state) {
            abort();
        }
        auto keepAlive = state;
        state->unsubscribe();
    }

    weak_state_type get_weak() {
        return state;
    }
    static subscription lock(weak_state_type w) {
        return subscription(w);
    }
};

inline bool operator<(const subscription& lhs, const subscription& rhs) {
    return lhs.state < rhs.state;
}
inline bool operator==(const subscription& lhs, const subscription& rhs) {
    return lhs.state == rhs.state;
}
inline bool operator!=(const subscription& lhs, const subscription& rhs) {
    return !(lhs == rhs);
}

inline auto make_subscription()
    ->      subscription {
    return  subscription();
}
template<class I>
auto make_subscription(I&& i)
    -> typename std::enable_if<!is_subscription<I>::value && !detail::is_unsubscribe_function<I>::value,
            subscription>::type {
    return  subscription(std::forward<I>(i));
}
template<class Unsubscribe>
auto make_subscription(Unsubscribe&& u)
    -> typename std::enable_if<detail::is_unsubscribe_function<Unsubscribe>::value,
            subscription>::type {
    return  subscription(static_subscription<Unsubscribe>(std::forward<Unsubscribe>(u)));
}

namespace detail {

struct tag_composite_subscription_empty {};

class composite_subscription_inner
{
private:
    typedef subscription::weak_state_type weak_subscription;
    struct composite_subscription_state : public std::enable_shared_from_this<composite_subscription_state>
    {
        std::set<subscription> subscriptions;
        std::mutex lock;
        std::atomic<bool> issubscribed;

        ~composite_subscription_state()
        {
            std::unique_lock<decltype(lock)> guard(lock);
            subscriptions.clear();
        }

        composite_subscription_state()
            : issubscribed(true)
        {
        }
        composite_subscription_state(tag_composite_subscription_empty)
            : issubscribed(false)
        {
        }

        inline weak_subscription add(subscription s) {
            if (!issubscribed) {
                s.unsubscribe();
            } else if (s.is_subscribed()) {
                std::unique_lock<decltype(lock)> guard(lock);
                subscriptions.insert(s);
            }
            return s.get_weak();
        }

        inline void remove(weak_subscription w) {
            if (issubscribed && !w.expired()) {
                auto s = subscription::lock(w);
                std::unique_lock<decltype(lock)> guard(lock);
                subscriptions.erase(std::move(s));
            }
        }

        inline void clear() {
            if (issubscribed) {
                std::unique_lock<decltype(lock)> guard(lock);

                std::set<subscription> v(std::move(subscriptions));
                guard.unlock();
                std::for_each(v.begin(), v.end(),
                              [](const subscription& s) {
                                s.unsubscribe(); });
            }
        }

        inline void unsubscribe() {
            if (issubscribed.exchange(false)) {
                std::unique_lock<decltype(lock)> guard(lock);

                std::set<subscription> v(std::move(subscriptions));
                guard.unlock();
                std::for_each(v.begin(), v.end(),
                              [](const subscription& s) {
                                s.unsubscribe(); });
            }
        }
    };

public:
    typedef std::shared_ptr<composite_subscription_state> shared_state_type;

protected:
    mutable shared_state_type state;

public:
    composite_subscription_inner()
        : state(std::make_shared<composite_subscription_state>())
    {
    }
    composite_subscription_inner(tag_composite_subscription_empty et)
        : state(std::make_shared<composite_subscription_state>(et))
    {
    }

    composite_subscription_inner(const composite_subscription_inner& o)
        : state(o.state)
    {
        if (!state) {
            abort();
        }
    }
    composite_subscription_inner(composite_subscription_inner&& o)
        : state(std::move(o.state))
    {
        if (!state) {
            abort();
        }
    }

    composite_subscription_inner& operator=(composite_subscription_inner o)
    {
        state = std::move(o.state);
        if (!state) {
            abort();
        }
        return *this;
    }

    inline weak_subscription add(subscription s) const {
        if (!state) {
            abort();
        }
        return state->add(std::move(s));
    }
    inline void remove(weak_subscription w) const {
        if (!state) {
            abort();
        }
        state->remove(std::move(w));
    }
    inline void clear() const {
        if (!state) {
            abort();
        }
        state->clear();
    }
    inline void unsubscribe() {
        if (!state) {
            abort();
        }
        state->unsubscribe();
    }
};

}

class composite_subscription
    : protected detail::composite_subscription_inner
    , public subscription
{
    typedef detail::composite_subscription_inner inner_type;
public:
    typedef subscription::weak_state_type weak_subscription;

    static composite_subscription shared_empty;

    composite_subscription(detail::tag_composite_subscription_empty et)
        : inner_type(et)
        , subscription() // use empty base
    {
    }

public:

    composite_subscription()
        : inner_type()
        , subscription(*static_cast<const inner_type* const>(this))
    {
    }

    composite_subscription(const composite_subscription& o)
        : inner_type(o)
        , subscription(static_cast<const subscription&>(o))
    {
    }
    composite_subscription(composite_subscription&& o)
        : inner_type(std::move(o))
        , subscription(std::move(static_cast<subscription&>(o)))
    {
    }

    composite_subscription& operator=(composite_subscription o)
    {
        inner_type::operator=(std::move(o));
        subscription::operator=(std::move(*static_cast<subscription*>(&o)));
        return *this;
    }

    static inline composite_subscription empty() {
        return shared_empty;
    }

    using subscription::is_subscribed;
    using subscription::unsubscribe;

    using inner_type::clear;

    inline weak_subscription add(subscription s) const {
        if (s == static_cast<const subscription&>(*this)) {
            // do not nest the same subscription
            abort();
            //return s.get_weak();
        }
        auto that = this->subscription::state.get();
        trace_activity().subscription_add_enter(*that, s);
        auto w = inner_type::add(std::move(s));
        trace_activity().subscription_add_return(*that);
        return w;
    }

    template<class F>
    auto add(F f) const
    -> typename std::enable_if<detail::is_unsubscribe_function<F>::value, weak_subscription>::type {
        return add(make_subscription(std::move(f)));
    }

    inline void remove(weak_subscription w) const {
        auto that = this->subscription::state.get();
        trace_activity().subscription_remove_enter(*that, w);
        inner_type::remove(w);
        trace_activity().subscription_remove_return(*that);
    }
};

inline bool operator<(const composite_subscription& lhs, const composite_subscription& rhs) {
    return static_cast<const subscription&>(lhs) < static_cast<const subscription&>(rhs);
}
inline bool operator==(const composite_subscription& lhs, const composite_subscription& rhs) {
    return static_cast<const subscription&>(lhs) == static_cast<const subscription&>(rhs);
}
inline bool operator!=(const composite_subscription& lhs, const composite_subscription& rhs) {
    return !(lhs == rhs);
}

//static
RXCPP_SELECT_ANY composite_subscription composite_subscription::shared_empty = composite_subscription(detail::tag_composite_subscription_empty());

template<class T>
class resource : public subscription_base
{
public:
    typedef typename composite_subscription::weak_subscription weak_subscription;

    resource()
        : lifetime(composite_subscription())
        , value(std::make_shared<rxu::detail::maybe<T>>())
    {
    }

    explicit resource(T t, composite_subscription cs = composite_subscription())
        : lifetime(std::move(cs))
        , value(std::make_shared<rxu::detail::maybe<T>>(rxu::detail::maybe<T>(std::move(t))))
    {
        auto localValue = value;
        lifetime.add(
            [localValue](){
                localValue->reset();
            }
        );
    }

    T& get() {
        return value.get()->get();
    }
    composite_subscription& get_subscription() {
        return lifetime;
    }

    bool is_subscribed() const {
        return lifetime.is_subscribed();
    }
    weak_subscription add(subscription s) const {
        return lifetime.add(std::move(s));
    }
    template<class F>
    auto add(F f) const
    -> typename std::enable_if<detail::is_unsubscribe_function<F>::value, weak_subscription>::type {
        return lifetime.add(make_subscription(std::move(f)));
    }
    void remove(weak_subscription w) const {
        return lifetime.remove(std::move(w));
    }
    void clear() const {
        return lifetime.clear();
    }
    void unsubscribe() const {
        return lifetime.unsubscribe();
    }

protected:
    composite_subscription lifetime;
    std::shared_ptr<rxu::detail::maybe<T>> value;
};

}

#endif
<|endoftext|>


<commit_before>/***
 * Copyright (c) 2013, Dan Hasting
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. 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.
 * 3. Neither the name of the organization 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 HOLDER 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 "thegamesdbscraper.h"

#include "../global.h"
#include "../common.h"

#include <QDir>
#include <QEventLoop>
#include <QJsonDocument>
#include <QJsonArray>
#include <QJsonObject>
#include <QMessageBox>
#include <QTextStream>
#include <QTimer>
#include <QUrl>

#include <QtNetwork/QNetworkAccessManager>
#include <QtNetwork/QNetworkReply>
#include <QtNetwork/QNetworkRequest>

TheGamesDBScraper::TheGamesDBScraper(QWidget *parent, bool force) : QObject(parent)
{
    this->parent = parent;
    this->force = force;
    this->keepGoing = true;
}

QString TheGamesDBScraper::convertIDs(QJsonObject foundGame, QString typeName, QString listName)
{
    QJsonArray idArray = foundGame.value(typeName).toArray();

    QString cacheFileString = getDataLocation() + "/cache/" + typeName + ".json";
    QFile cacheFile(cacheFileString);

    cacheFile.open(QIODevice::ReadOnly);
    QString data = cacheFile.readAll();
    cacheFile.close();

    QJsonDocument document = QJsonDocument::fromJson(data.toUtf8());
    QJsonObject cache = document.object();

    QString result = "";

    foreach (QJsonValue id, idArray)
    {
        QString entryID = QString::number(id.toInt());
        QString entryName = cache.value(entryID).toObject().value("name").toString();

        if (entryName == "") {
            updateListCache(&cacheFile, listName);
            cacheFile.open(QIODevice::ReadOnly);
            data = cacheFile.readAll();
            cacheFile.close();

            document = QJsonDocument::fromJson(data.toUtf8());
            entryName = cache.value(entryID).toObject().value("name").toString();
        }

        if (entryName != "")
            result += entryName + ", ";
    }

    int pos = result.lastIndexOf(QChar(','));
    return result.left(pos);
}

void TheGamesDBScraper::deleteGameInfo(QString fileName, QString identifier)
{
    QString text;
    text = QString(tr("NOTE: If you are deleting this game's information because the game doesn't "))
                 + tr("exist on TheGamesDB and <AppName> pulled the information for different game, it's ")
                 + tr("better to create an account on")+" TheGamesDB "
                 + tr("and add the game so other users can benefit as well.")
                 + "

"
                 + tr("This will cause <AppName> to not update the information for this game until you ")
                 + tr("force it with \"Download/Update Info...\"")
                 + "

"
                 + tr("Delete the current information for") + " " + fileName + "?";
    text.replace("<AppName>",AppName);

    int answer = QMessageBox::question(parent, tr("Delete Game Information"), text,
                                       QMessageBox::Yes | QMessageBox::No);

    if (answer == QMessageBox::Yes) {
        QString gameCache = getDataLocation() + "/cache/" + identifier.toLower();

        QString dataFile = gameCache + "/data.xml";
        QFile file(dataFile);

        // Remove game information
        file.open(QIODevice::WriteOnly);
        QTextStream stream(&file);
        stream << "NULL";
        file.close();

        // Remove cover image
        QString coverFile = gameCache + "/boxart-front.";

        QFile coverJPG(coverFile + "jpg");
        QFile coverPNG(coverFile + "png");

        if (coverJPG.exists())
            coverJPG.remove();
        if (coverPNG.exists())
            coverPNG.remove();

        coverJPG.open(QIODevice::WriteOnly);
        QTextStream streamImage(&coverJPG);
        streamImage << "";
        coverJPG.close();
    }
}

void TheGamesDBScraper::downloadGameInfo(QString identifier, QString searchName, QString gameID)
{
    if (keepGoing && identifier != "") {
        if (force) parent->setEnabled(false);

        bool updated = false;

        QString gameCache = getDataLocation() + "/cache/" + identifier.toLower();
        QDir cache(gameCache);

        if (!cache.exists()) {
            cache.mkpath(gameCache);
        }

        QString genreCache = getDataLocation() + "/cache/genres.json";
        QFile genres(genreCache);
        if (!genres.exists())
            updateListCache(&genres, "Genres");

        QString developerCache = getDataLocation() + "/cache/developers.json";
        QFile developers(developerCache);
        if (!developers.exists())
            updateListCache(&developers, "Developers");

        QString publisherCache = getDataLocation() + "/cache/publishers.json";
        QFile publishers(publisherCache);
        if (!publishers.exists())
            updateListCache(&publishers, "Publishers");

        //Get game JSON info from thegamesdb.net
        QString dataFile = gameCache + "/data.json";
        QFile file(dataFile);

        if (!file.exists() || file.size() == 0 || force) {
            QUrl url;

            //Remove [!], (U), etc. from GoodName for searching
            searchName.remove(QRegExp("\\W*(\\(|\\[).+(\\)|\\])\\W*"));

            //Few game specific hacks
            //TODO: Contact thegamesdb.net and see if these can be fixed on their end
            if (searchName == "Legend of Zelda, The - Majora's Mask")
                searchName = "Majora's Mask";
            else if (searchName == "Legend of Zelda, The - Ocarina of Time" ||
                     searchName == "THE LEGEND OF ZELDA")
                searchName = "The Legend of Zelda: Ocarina of Time";
            else if (searchName == "Tsumi to Batsu - Hoshi no Keishousha")
                searchName = "Sin and Punishment";
            else if (searchName == "1080 Snowboarding")
                searchName = "1080: TenEighty Snowboarding";
            else if (searchName == "Extreme-G XG2")
                searchName = "Extreme-G 2";
            else if (searchName.contains("Pokemon"))
                searchName.replace("Pokemon", "Pokémon");
            else if (searchName.toLower() == "f-zero x")
                gameID = "10836";

            QString apiFilter = "&filter[platform]=3&include=boxart&fields=game_title,release_date,";
            apiFilter += "developers,publishers,genres,overview,rating,players";

            //If user submits gameID, use that
            if (gameID != "")
                url.setUrl("https://api.thegamesdb.net/Games/ByGameID?apikey=" + TheGamesDBAPIKey + "&id="
                           + gameID + apiFilter);
            else
                url.setUrl("https://api.thegamesdb.net/Games/ByGameName?apikey=" + TheGamesDBAPIKey + "&name="
                           + searchName + apiFilter);

            QString data = getUrlContents(url);

            QJsonDocument document = QJsonDocument::fromJson(data.toUtf8());
            QJsonObject json = document.object();

            if (json.value("code").toInt() != 200 && json.value("code").toInt() != 0) {
                QString status = json.value("status").toString();
                QString message;
                message = QString(tr("The following error from TheGamesDB occured while downloading:"))
                                   + "

" + status;
                QMessageBox::information(parent, QObject::tr("Download Error"), message);
                if (force) parent->setEnabled(true);
                return;
            }

            QJsonValue games = json.value("data").toObject().value("games");
            QJsonArray gamesArray = games.toArray();

            int count = 0, found = 0;

            foreach (QJsonValue game, gamesArray)
            {
                QJsonValue title = game.toObject().value("game_title");

                if (force) { //from user dialog
                    QJsonValue date = game.toObject().value("release_date");

                    QString check = "Game: " + title.toString();
                    check.remove(QRegExp(QString("[^A-Za-z 0-9 \\.,\\?'""!@#\\$%\\^&\\*\\")
                                         + "(\\)-_=\\+;:<>\\/\\\\|\\}\\{\\[\\]`~]*"));
                    if (date.toString() != "") check += "\n" + tr("Released on: ") + date.toString();
                    check += "\n\n" + tr("Does this look correct?");

                    int answer = QMessageBox::question(parent, QObject::tr("Game Information Download"),
                                                       check, QMessageBox::Yes | QMessageBox::No);

                    if (answer == QMessageBox::Yes) {
                        found = count;
                        updated = true;
                        break;
                    }
                } else {
                    //We only want one game, so search for a perfect match in the GameTitle element.
                    //Otherwise this will default to 0 (the first game found)
                    if(title.toString() == searchName)
                        found = count;
                }

                count++;
            }

            if (!force || updated) {
                QJsonObject foundGame = gamesArray.at(found).toObject();
                QJsonObject saveData;

                QString gameID = QString::number(foundGame.value("id").toInt());
                QJsonObject boxart = json.value("include").toObject().value("boxart").toObject();

                QString thumbURL = boxart.value("base_url").toObject().value("thumb").toString();
                QJsonArray imgArray = boxart.value("data").toObject().value(gameID).toArray();

                QString frontImg = "";

                foreach (QJsonValue img, imgArray)
                {
                    QString type = img.toObject().value("type").toString();
                    QString side = img.toObject().value("side").toString();
                    QString filename = img.toObject().value("filename").toString();

                    if (type == "boxart" && side == "front")
                        frontImg = thumbURL + filename;
                }

                //Convert IDs from API to text names
                QString genresString = convertIDs(foundGame, "genres", "Genres");
                QString developerString = convertIDs(foundGame, "developers", "Developers");
                QString publisherString = convertIDs(foundGame, "publishers", "Publishers");

                saveData.insert("game_title", foundGame.value("game_title"));
                saveData.insert("release_date", foundGame.value("release_date"));
                saveData.insert("rating", foundGame.value("rating"));
                saveData.insert("overview", foundGame.value("overview"));
                saveData.insert("players", foundGame.value("players"));
                saveData.insert("boxart", frontImg);
                saveData.insert("genres", genresString);
                saveData.insert("developer", developerString);
                saveData.insert("publisher", publisherString);

                QJsonDocument document(saveData);

                file.open(QIODevice::WriteOnly);
                file.write(document.toJson());
                file.close();
            }

            if (force && !updated) {
                QString message;

                if (count == 0)
                    message = QObject::tr("No results found.");
                else
                    message = QObject::tr("No more results found.");

                QMessageBox::information(parent, QObject::tr("Game Information Download"), message);
            }
        }

        //Get front cover
        QString boxartURL = "";
        QString boxartExt = "";
        QString coverFile = gameCache + "/boxart-front.";

        QFile coverJPG(coverFile + "jpg");
        QFile coverPNG(coverFile + "png");

        if ((!coverJPG.exists() && !coverPNG.exists()) || (force && updated)) {
            file.open(QIODevice::ReadOnly);
            QString data = file.readAll();
            file.close();

            QJsonDocument document = QJsonDocument::fromJson(data.toUtf8());
            QJsonObject json = document.object();
            QString boxartURL = json.value("boxart").toString();

            if (boxartURL != "") {
                QUrl url(boxartURL);

                //Delete current box art
                QFile::remove(coverFile + "jpg");
                QFile::remove(coverFile + "png");

                //Check to save as JPG or PNG
                boxartExt = QFileInfo(boxartURL).completeSuffix().toLower();
                QFile cover(coverFile + boxartExt);

                cover.open(QIODevice::WriteOnly);
                cover.write(getUrlContents(url));
                cover.close();
            }
        }

        if (updated)
            QMessageBox::information(parent, QObject::tr("Game Information Download"),
                                     QObject::tr("Download Complete!"));

        if (force) parent->setEnabled(true);
    }
}

QByteArray TheGamesDBScraper::getUrlContents(QUrl url)
{
    QNetworkAccessManager *manager = new QNetworkAccessManager;

    QNetworkRequest request;
    request.setUrl(url);
    request.setRawHeader("User-Agent", AppName.toUtf8().constData());
    QNetworkReply *reply = manager->get(request);

    QTimer timer;
    timer.setSingleShot(true);

    QEventLoop loop;
    connect(reply, SIGNAL(finished()), &loop, SLOT(quit()));
    connect(&timer, SIGNAL(timeout()), &loop, SLOT(quit()));

    int time = SETTINGS.value("Other/networktimeout", 10).toInt();
    if (time == 0) time = 10;
    time *= 1000;

    timer.start(time);
    loop.exec();

    if(timer.isActive()) { //Got reply
        timer.stop();

        if(reply->error() > 0)
            showError(reply->errorString());
        else
            return reply->readAll();

    } else //Request timed out
        showError(tr("Request timed out. Check your network settings."));

    return QByteArray();
}

void TheGamesDBScraper::showError(QString error)
{
    QString question = "\n\n" + tr("Continue scraping information?");

    if (force)
        QMessageBox::information(parent, tr("Network Error"), error);
    else {
        int answer = QMessageBox::question(parent, tr("Network Error"), error + question,
                                           QMessageBox::Yes | QMessageBox::No);

        if (answer == QMessageBox::No)
            keepGoing = false;
    }
}

void TheGamesDBScraper::updateListCache(QFile *file, QString list)
{
    QUrl url;
    url.setUrl("https://api.thegamesdb.net/" + list + "?apikey=" + TheGamesDBAPIKey);
    QString data = getUrlContents(url);
    QJsonDocument document = QJsonDocument::fromJson(data.toUtf8());
    QJsonDocument result(document.object().value("data").toObject().value(list.toLower()).toObject());

    file->open(QIODevice::WriteOnly);
    file->write(result.toJson());
    file->close();
}
<commit_msg>Use showError method and fill in empty strings for missing information<commit_after>/***
 * Copyright (c) 2013, Dan Hasting
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. 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.
 * 3. Neither the name of the organization 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 HOLDER 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 "thegamesdbscraper.h"

#include "../global.h"
#include "../common.h"

#include <QDir>
#include <QEventLoop>
#include <QJsonDocument>
#include <QJsonArray>
#include <QJsonObject>
#include <QMessageBox>
#include <QTextStream>
#include <QTimer>
#include <QUrl>

#include <QtNetwork/QNetworkAccessManager>
#include <QtNetwork/QNetworkReply>
#include <QtNetwork/QNetworkRequest>

TheGamesDBScraper::TheGamesDBScraper(QWidget *parent, bool force) : QObject(parent)
{
    this->parent = parent;
    this->force = force;
    this->keepGoing = true;
}

QString TheGamesDBScraper::convertIDs(QJsonObject foundGame, QString typeName, QString listName)
{
    QJsonArray idArray = foundGame.value(typeName).toArray();

    QString cacheFileString = getDataLocation() + "/cache/" + typeName + ".json";
    QFile cacheFile(cacheFileString);

    cacheFile.open(QIODevice::ReadOnly);
    QString data = cacheFile.readAll();
    cacheFile.close();

    QJsonDocument document = QJsonDocument::fromJson(data.toUtf8());
    QJsonObject cache = document.object();

    QString result = "";

    foreach (QJsonValue id, idArray)
    {
        QString entryID = QString::number(id.toInt());
        QString entryName = cache.value(entryID).toObject().value("name").toString();

        if (entryName == "") {
            updateListCache(&cacheFile, listName);
            cacheFile.open(QIODevice::ReadOnly);
            data = cacheFile.readAll();
            cacheFile.close();

            document = QJsonDocument::fromJson(data.toUtf8());
            entryName = cache.value(entryID).toObject().value("name").toString();
        }

        if (entryName != "")
            result += entryName + ", ";
    }

    int pos = result.lastIndexOf(QChar(','));
    return result.left(pos);
}

void TheGamesDBScraper::deleteGameInfo(QString fileName, QString identifier)
{
    QString text;
    text = QString(tr("NOTE: If you are deleting this game's information because the game doesn't "))
                 + tr("exist on TheGamesDB and <AppName> pulled the information for different game, it's ")
                 + tr("better to create an account on")+" TheGamesDB "
                 + tr("and add the game so other users can benefit as well.")
                 + "

"
                 + tr("This will cause <AppName> to not update the information for this game until you ")
                 + tr("force it with \"Download/Update Info...\"")
                 + "

"
                 + tr("Delete the current information for") + " " + fileName + "?";
    text.replace("<AppName>",AppName);

    int answer = QMessageBox::question(parent, tr("Delete Game Information"), text,
                                       QMessageBox::Yes | QMessageBox::No);

    if (answer == QMessageBox::Yes) {
        QString gameCache = getDataLocation() + "/cache/" + identifier.toLower();

        QString dataFile = gameCache + "/data.xml";
        QFile file(dataFile);

        // Remove game information
        file.open(QIODevice::WriteOnly);
        QTextStream stream(&file);
        stream << "NULL";
        file.close();

        // Remove cover image
        QString coverFile = gameCache + "/boxart-front.";

        QFile coverJPG(coverFile + "jpg");
        QFile coverPNG(coverFile + "png");

        if (coverJPG.exists())
            coverJPG.remove();
        if (coverPNG.exists())
            coverPNG.remove();

        coverJPG.open(QIODevice::WriteOnly);
        QTextStream streamImage(&coverJPG);
        streamImage << "";
        coverJPG.close();
    }
}

void TheGamesDBScraper::downloadGameInfo(QString identifier, QString searchName, QString gameID)
{
    if (keepGoing && identifier != "") {
        if (force) parent->setEnabled(false);

        bool updated = false;

        QString gameCache = getDataLocation() + "/cache/" + identifier.toLower();
        QDir cache(gameCache);

        if (!cache.exists()) {
            cache.mkpath(gameCache);
        }

        QString genreCache = getDataLocation() + "/cache/genres.json";
        QFile genres(genreCache);
        if (!genres.exists())
            updateListCache(&genres, "Genres");

        QString developerCache = getDataLocation() + "/cache/developers.json";
        QFile developers(developerCache);
        if (!developers.exists())
            updateListCache(&developers, "Developers");

        QString publisherCache = getDataLocation() + "/cache/publishers.json";
        QFile publishers(publisherCache);
        if (!publishers.exists())
            updateListCache(&publishers, "Publishers");

        //Get game JSON info from thegamesdb.net
        QString dataFile = gameCache + "/data.json";
        QFile file(dataFile);

        if (!file.exists() || file.size() == 0 || force) {
            QUrl url;

            //Remove [!], (U), etc. from GoodName for searching
            searchName.remove(QRegExp("\\W*(\\(|\\[).+(\\)|\\])\\W*"));

            //Few game specific hacks
            //TODO: Contact thegamesdb.net and see if these can be fixed on their end
            if (searchName == "Legend of Zelda, The - Majora's Mask")
                searchName = "Majora's Mask";
            else if (searchName == "Legend of Zelda, The - Ocarina of Time" ||
                     searchName == "THE LEGEND OF ZELDA")
                searchName = "The Legend of Zelda: Ocarina of Time";
            else if (searchName == "Tsumi to Batsu - Hoshi no Keishousha")
                searchName = "Sin and Punishment";
            else if (searchName == "1080 Snowboarding")
                searchName = "1080: TenEighty Snowboarding";
            else if (searchName == "Extreme-G XG2")
                searchName = "Extreme-G 2";
            else if (searchName.contains("Pokemon"))
                searchName.replace("Pokemon", "Pokémon");
            else if (searchName.toLower() == "f-zero x")
                gameID = "10836";

            QString apiFilter = "&filter[platform]=3&include=boxart&fields=game_title,release_date,";
            apiFilter += "developers,publishers,genres,overview,rating,players";

            //If user submits gameID, use that
            if (gameID != "")
                url.setUrl("https://api.thegamesdb.net/Games/ByGameID?apikey=" + TheGamesDBAPIKey + "&id="
                           + gameID + apiFilter);
            else
                url.setUrl("https://api.thegamesdb.net/Games/ByGameName?apikey=" + TheGamesDBAPIKey + "&name="
                           + searchName + apiFilter);

            QString data = getUrlContents(url);

            QJsonDocument document = QJsonDocument::fromJson(data.toUtf8());
            QJsonObject json = document.object();

            if (json.value("code").toInt() != 200 && json.value("code").toInt() != 0) {
                QString status = json.value("status").toString();
                QString message;
                message = QString(tr("The following error from TheGamesDB occured while downloading:"))
                                   + "

" + status + "

";
                showError(message);
                if (force) parent->setEnabled(true);
                return;
            }

            QJsonValue games = json.value("data").toObject().value("games");
            QJsonArray gamesArray = games.toArray();

            int count = 0, found = 0;

            foreach (QJsonValue game, gamesArray)
            {
                QJsonValue title = game.toObject().value("game_title");

                if (force) { //from user dialog
                    QJsonValue date = game.toObject().value("release_date");

                    QString check = "Game: " + title.toString();
                    check.remove(QRegExp(QString("[^A-Za-z 0-9 \\.,\\?'""!@#\\$%\\^&\\*\\")
                                         + "(\\)-_=\\+;:<>\\/\\\\|\\}\\{\\[\\]`~]*"));
                    if (date.toString() != "") check += "\n" + tr("Released on: ") + date.toString();
                    check += "\n\n" + tr("Does this look correct?");

                    int answer = QMessageBox::question(parent, QObject::tr("Game Information Download"),
                                                       check, QMessageBox::Yes | QMessageBox::No);

                    if (answer == QMessageBox::Yes) {
                        found = count;
                        updated = true;
                        break;
                    }
                } else {
                    //We only want one game, so search for a perfect match in the GameTitle element.
                    //Otherwise this will default to 0 (the first game found)
                    if(title.toString() == searchName)
                        found = count;
                }

                count++;
            }

            if (!force || updated) {
                QJsonObject foundGame = gamesArray.at(found).toObject();
                QJsonObject saveData;

                QString gameID = QString::number(foundGame.value("id").toInt());
                QJsonObject boxart = json.value("include").toObject().value("boxart").toObject();

                QString thumbURL = boxart.value("base_url").toObject().value("thumb").toString();
                QJsonArray imgArray = boxart.value("data").toObject().value(gameID).toArray();

                QString frontImg = "";

                foreach (QJsonValue img, imgArray)
                {
                    QString type = img.toObject().value("type").toString();
                    QString side = img.toObject().value("side").toString();
                    QString filename = img.toObject().value("filename").toString();

                    if (type == "boxart" && side == "front")
                        frontImg = thumbURL + filename;
                }

                //Convert IDs from API to text names
                QString genresString = convertIDs(foundGame, "genres", "Genres");
                QString developerString = convertIDs(foundGame, "developers", "Developers");
                QString publisherString = convertIDs(foundGame, "publishers", "Publishers");

                saveData.insert("game_title", foundGame.value("game_title").toString());
                saveData.insert("release_date", foundGame.value("release_date").toString());
                saveData.insert("rating", foundGame.value("rating").toString());
                saveData.insert("overview", foundGame.value("overview").toString());
                saveData.insert("players", QString::number(foundGame.value("players").toInt()));
                saveData.insert("boxart", frontImg);
                saveData.insert("genres", genresString);
                saveData.insert("developer", developerString);
                saveData.insert("publisher", publisherString);

                QJsonDocument document(saveData);

                file.open(QIODevice::WriteOnly);
                file.write(document.toJson());
                file.close();
            }

            if (force && !updated) {
                QString message;

                if (count == 0)
                    message = QObject::tr("No results found.");
                else
                    message = QObject::tr("No more results found.");

                QMessageBox::information(parent, QObject::tr("Game Information Download"), message);
            }
        }

        //Get front cover
        QString boxartURL = "";
        QString boxartExt = "";
        QString coverFile = gameCache + "/boxart-front.";

        QFile coverJPG(coverFile + "jpg");
        QFile coverPNG(coverFile + "png");

        if ((!coverJPG.exists() && !coverPNG.exists()) || (force && updated)) {
            file.open(QIODevice::ReadOnly);
            QString data = file.readAll();
            file.close();

            QJsonDocument document = QJsonDocument::fromJson(data.toUtf8());
            QJsonObject json = document.object();
            QString boxartURL = json.value("boxart").toString();

            if (boxartURL != "") {
                QUrl url(boxartURL);

                //Delete current box art
                QFile::remove(coverFile + "jpg");
                QFile::remove(coverFile + "png");

                //Check to save as JPG or PNG
                boxartExt = QFileInfo(boxartURL).completeSuffix().toLower();
                QFile cover(coverFile + boxartExt);

                cover.open(QIODevice::WriteOnly);
                cover.write(getUrlContents(url));
                cover.close();
            }
        }

        if (updated)
            QMessageBox::information(parent, QObject::tr("Game Information Download"),
                                     QObject::tr("Download Complete!"));

        if (force) parent->setEnabled(true);
    }
}

QByteArray TheGamesDBScraper::getUrlContents(QUrl url)
{
    QNetworkAccessManager *manager = new QNetworkAccessManager;

    QNetworkRequest request;
    request.setUrl(url);
    request.setRawHeader("User-Agent", AppName.toUtf8().constData());
    QNetworkReply *reply = manager->get(request);

    QTimer timer;
    timer.setSingleShot(true);

    QEventLoop loop;
    connect(reply, SIGNAL(finished()), &loop, SLOT(quit()));
    connect(&timer, SIGNAL(timeout()), &loop, SLOT(quit()));

    int time = SETTINGS.value("Other/networktimeout", 10).toInt();
    if (time == 0) time = 10;
    time *= 1000;

    timer.start(time);
    loop.exec();

    if(timer.isActive()) { //Got reply
        timer.stop();

        if(reply->error() > 0)
            showError(reply->errorString());
        else
            return reply->readAll();

    } else //Request timed out
        showError(tr("Request timed out. Check your network settings."));

    return QByteArray();
}

void TheGamesDBScraper::showError(QString error)
{
    QString question = "\n\n" + tr("Continue scraping information?");

    if (force)
        QMessageBox::information(parent, tr("Network Error"), error);
    else {
        int answer = QMessageBox::question(parent, tr("Network Error"), error + question,
                                           QMessageBox::Yes | QMessageBox::No);

        if (answer == QMessageBox::No)
            keepGoing = false;
    }
}

void TheGamesDBScraper::updateListCache(QFile *file, QString list)
{
    QUrl url;
    url.setUrl("https://api.thegamesdb.net/" + list + "?apikey=" + TheGamesDBAPIKey);
    QString data = getUrlContents(url);
    QJsonDocument document = QJsonDocument::fromJson(data.toUtf8());
    QJsonDocument result(document.object().value("data").toObject().value(list.toLower()).toObject());

    file->open(QIODevice::WriteOnly);
    file->write(result.toJson());
    file->close();
}
<|endoftext|>


<commit_before>#include "stdafx.h"
#include "util/fixes.h"
#include "dispatcher.h"
#include "condition.h"

#include "gamesystems/objects/objsystem.h"
#include "ui/ui_systems.h"
#include "ui/ui_legacysystems.h"

#define CONDFIX(fname) static int fname ## (DispatcherCallbackArgs args);
#define HOOK_ORG(fname) static int (__cdecl* org ##fname)(DispatcherCallbackArgs) = replaceFunction<int(__cdecl)(DispatcherCallbackArgs)>

class GeneralConditionFixes : public TempleFix {
public:

	static int WeaponKeenQuery(DispatcherCallbackArgs args);

	void apply() override {

		{ // Fix for duplicate Blackguard tooltip when fallen paladin
			SubDispDefNew sdd;
			sdd.dispKey = DK_NONE;
			sdd.dispType = dispTypeEffectTooltip;
			sdd.dispCallback = [](DispatcherCallbackArgs args) {

				GET_DISPIO(dispIOTypeEffectTooltip, DispIoEffectTooltip);

				if (objects.StatLevelGet(args.objHndCaller, stat_level_blackguard))
					return 0;
				dispIo->Append(args.GetData1(), -1, nullptr);
				return 0;
			};

			write(0x102E7400, &sdd, sizeof(sdd));
		}

		replaceFunction(0x100FF670, WeaponKeenQuery);

	}
} genCondFixes;

int GeneralConditionFixes::WeaponKeenQuery(DispatcherCallbackArgs args){
	GET_DISPIO(dispIOTypeQuery, DispIoD20Query);
	dispIo->return_val = 2; // default

	auto itemHndl = uiSystems->GetChar().GetTooltipItem();
	if (!itemHndl || !objSystem->IsValidHandle(itemHndl))
		return 0;

	// meh, it doesn't have a handle
	/*auto invIdx = args.GetCondArg(2);
	auto itemHndl = inventory.GetItemAtInvIdx(args.objHndCaller, invIdx);
	if (!itemHndl || !objSystem->IsValidHandle(itemHndl))
		return 0;
		*/
	auto item = objSystem->GetObject(itemHndl);

	auto critRange = item->GetInt32(obj_f_weapon_crit_range);
	dispIo->return_val = critRange;

	return 0;
}
<commit_msg>Fixed bad Fallen Paladin effect tooltip<commit_after>#include "stdafx.h"
#include "util/fixes.h"
#include "dispatcher.h"
#include "condition.h"

#include "gamesystems/objects/objsystem.h"
#include "ui/ui_systems.h"
#include "ui/ui_legacysystems.h"

#define CONDFIX(fname) static int fname ## (DispatcherCallbackArgs args);
#define HOOK_ORG(fname) static int (__cdecl* org ##fname)(DispatcherCallbackArgs) = replaceFunction<int(__cdecl)(DispatcherCallbackArgs)>

class GeneralConditionFixes : public TempleFix {
public:

	static int WeaponKeenQuery(DispatcherCallbackArgs args);

	void apply() override {

		{ // Fix for duplicate Blackguard tooltip when fallen paladin
			SubDispDefNew sdd;
			sdd.dispKey = DK_NONE;
			sdd.dispType = dispTypeEffectTooltip;
			sdd.dispCallback = [](DispatcherCallbackArgs args) {

				GET_DISPIO(dispIOTypeEffectTooltip, DispIoEffectTooltip);

				if (objects.StatLevelGet(args.objHndCaller, stat_level_blackguard))
					return 0;
				dispIo->Append(args.GetData1(), -1, nullptr);
				return 0;
			};
			sdd.data1.sVal = 0xAF;
			write(0x102E7400, &sdd, sizeof(sdd));
		}

		replaceFunction(0x100FF670, WeaponKeenQuery);

	}
} genCondFixes;

int GeneralConditionFixes::WeaponKeenQuery(DispatcherCallbackArgs args){
	GET_DISPIO(dispIOTypeQuery, DispIoD20Query);
	dispIo->return_val = 2; // default

	auto itemHndl = uiSystems->GetChar().GetTooltipItem();
	if (!itemHndl || !objSystem->IsValidHandle(itemHndl))
		return 0;

	// meh, it doesn't have a handle
	/*auto invIdx = args.GetCondArg(2);
	auto itemHndl = inventory.GetItemAtInvIdx(args.objHndCaller, invIdx);
	if (!itemHndl || !objSystem->IsValidHandle(itemHndl))
		return 0;
		*/
	auto item = objSystem->GetObject(itemHndl);

	auto critRange = item->GetInt32(obj_f_weapon_crit_range);
	dispIo->return_val = critRange;

	return 0;
}
<|endoftext|>


<commit_before>/*
-----------------------------------------------------------------------------
This source file is part of OGRE
(Object-oriented Graphics Rendering Engine)
For the latest info, see http://www.ogre3d.org/

Copyright (c) 2000-2013 Torus Knot Software Ltd

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
-----------------------------------------------------------------------------
*/
#include "ZipArchiveTests.h"
#include "OgreZip.h"

#if OGRE_PLATFORM == OGRE_PLATFORM_APPLE
#include "macUtils.h"
#endif

using namespace Ogre;

// Register the suite
CPPUNIT_TEST_SUITE_REGISTRATION( ZipArchiveTests );

void ZipArchiveTests::setUp()
{
#if OGRE_PLATFORM == OGRE_PLATFORM_APPLE
    testPath = macBundlePath() + "/Contents/Resources/Media/misc/ArchiveTest.zip";
#elif OGRE_PLATFORM == OGRE_PLATFORM_WIN32
    testPath = "./Tests/OgreMain/misc/ArchiveTest.zip";
#else
    testPath = "../Tests/OgreMain/misc/ArchiveTest.zip";
#endif
}
void ZipArchiveTests::tearDown()
{
}

void ZipArchiveTests::testListNonRecursive()
{
    ZipArchive arch(testPath, "Zip");
    arch.load();
    StringVectorPtr vec = arch.list(false);

    CPPUNIT_ASSERT_EQUAL((size_t)2, vec->size());
    CPPUNIT_ASSERT_EQUAL(String("rootfile.txt"), vec->at(0));
    CPPUNIT_ASSERT_EQUAL(String("rootfile2.txt"), vec->at(1));

}
void ZipArchiveTests::testListRecursive()
{
    ZipArchive arch(testPath, "Zip");
    arch.load();
    StringVectorPtr vec = arch.list(true);

    CPPUNIT_ASSERT_EQUAL((size_t)6, vec->size());
    CPPUNIT_ASSERT_EQUAL(String("file.material"), vec->at(0));
    CPPUNIT_ASSERT_EQUAL(String("file2.material"), vec->at(1));
    CPPUNIT_ASSERT_EQUAL(String("file3.material"), vec->at(2));
    CPPUNIT_ASSERT_EQUAL(String("file4.material"), vec->at(3));
    CPPUNIT_ASSERT_EQUAL(String("rootfile.txt"), vec->at(4));
    CPPUNIT_ASSERT_EQUAL(String("rootfile2.txt"), vec->at(5));
}
void ZipArchiveTests::testListFileInfoNonRecursive()
{
    ZipArchive arch(testPath, "Zip");
    arch.load();
    FileInfoListPtr vec = arch.listFileInfo(false);

    CPPUNIT_ASSERT_EQUAL((size_t)2, vec->size());
    FileInfo& fi1 = vec->at(0);
    CPPUNIT_ASSERT_EQUAL(String("rootfile.txt"), fi1.filename);
    CPPUNIT_ASSERT_EQUAL(StringUtil::BLANK, fi1.path);
    CPPUNIT_ASSERT_EQUAL((size_t)40, fi1.compressedSize);
    CPPUNIT_ASSERT_EQUAL((size_t)130, fi1.uncompressedSize);

    FileInfo& fi2 = vec->at(1);
    CPPUNIT_ASSERT_EQUAL(String("rootfile2.txt"), fi2.filename);
    CPPUNIT_ASSERT_EQUAL(StringUtil::BLANK, fi2.path);
    CPPUNIT_ASSERT_EQUAL((size_t)45, fi2.compressedSize);
    CPPUNIT_ASSERT_EQUAL((size_t)156, fi2.uncompressedSize);
}
void ZipArchiveTests::testListFileInfoRecursive()
{
    ZipArchive arch(testPath, "Zip");
    arch.load();
    FileInfoListPtr vec = arch.listFileInfo(true);

    CPPUNIT_ASSERT_EQUAL((size_t)6, vec->size());
    FileInfo& fi3 = vec->at(0);
    CPPUNIT_ASSERT_EQUAL(String("file.material"), fi3.filename);
    CPPUNIT_ASSERT_EQUAL(String("level1/materials/scripts/"), fi3.path);
    CPPUNIT_ASSERT_EQUAL((size_t)0, fi3.compressedSize);
    CPPUNIT_ASSERT_EQUAL((size_t)0, fi3.uncompressedSize);

    FileInfo& fi4 = vec->at(1);
    CPPUNIT_ASSERT_EQUAL(String("file2.material"), fi4.filename);
    CPPUNIT_ASSERT_EQUAL(String("level1/materials/scripts/"), fi4.path);
    CPPUNIT_ASSERT_EQUAL((size_t)0, fi4.compressedSize);
    CPPUNIT_ASSERT_EQUAL((size_t)0, fi4.uncompressedSize);

    FileInfo& fi5 = vec->at(2);
    CPPUNIT_ASSERT_EQUAL(String("file3.material"), fi5.filename);
    CPPUNIT_ASSERT_EQUAL(String("level2/materials/scripts/"), fi5.path);
    CPPUNIT_ASSERT_EQUAL((size_t)0, fi5.compressedSize);
    CPPUNIT_ASSERT_EQUAL((size_t)0, fi5.uncompressedSize);

    FileInfo& fi6 = vec->at(3);
    CPPUNIT_ASSERT_EQUAL(String("file4.material"), fi6.filename);
    CPPUNIT_ASSERT_EQUAL(String("level2/materials/scripts/"), fi6.path);
    CPPUNIT_ASSERT_EQUAL((size_t)0, fi6.compressedSize);
    CPPUNIT_ASSERT_EQUAL((size_t)0, fi6.uncompressedSize);

    FileInfo& fi1 = vec->at(4);
    CPPUNIT_ASSERT_EQUAL(String("rootfile.txt"), fi1.filename);
    CPPUNIT_ASSERT_EQUAL(StringUtil::BLANK, fi1.path);
    CPPUNIT_ASSERT_EQUAL((size_t)40, fi1.compressedSize);
    CPPUNIT_ASSERT_EQUAL((size_t)130, fi1.uncompressedSize);

    FileInfo& fi2 = vec->at(5);
    CPPUNIT_ASSERT_EQUAL(String("rootfile2.txt"), fi2.filename);
    CPPUNIT_ASSERT_EQUAL(StringUtil::BLANK, fi2.path);
    CPPUNIT_ASSERT_EQUAL((size_t)45, fi2.compressedSize);
    CPPUNIT_ASSERT_EQUAL((size_t)156, fi2.uncompressedSize);

}
void ZipArchiveTests::testFindNonRecursive()
{
    ZipArchive arch(testPath, "Zip");
    arch.load();
    StringVectorPtr vec = arch.find("*.txt", false);

    CPPUNIT_ASSERT_EQUAL((size_t)2, vec->size());
    CPPUNIT_ASSERT_EQUAL(String("rootfile.txt"), vec->at(0));
    CPPUNIT_ASSERT_EQUAL(String("rootfile2.txt"), vec->at(1));
}
void ZipArchiveTests::testFindRecursive()
{
    ZipArchive arch(testPath, "Zip");
    arch.load();
    StringVectorPtr vec = arch.find("*.material", true);

    CPPUNIT_ASSERT_EQUAL((size_t)4, vec->size());
    CPPUNIT_ASSERT_EQUAL(String("file.material"), vec->at(0));
    CPPUNIT_ASSERT_EQUAL(String("file2.material"), vec->at(1));
    CPPUNIT_ASSERT_EQUAL(String("file3.material"), vec->at(2));
    CPPUNIT_ASSERT_EQUAL(String("file4.material"), vec->at(3));
}
void ZipArchiveTests::testFindFileInfoNonRecursive()
{
    ZipArchive arch(testPath, "Zip");
    arch.load();
    FileInfoListPtr vec = arch.findFileInfo("*.txt", false);

    CPPUNIT_ASSERT_EQUAL((size_t)2, vec->size());
    FileInfo& fi1 = vec->at(0);
    CPPUNIT_ASSERT_EQUAL(String("rootfile.txt"), fi1.filename);
    CPPUNIT_ASSERT_EQUAL(StringUtil::BLANK, fi1.path);
    CPPUNIT_ASSERT_EQUAL((size_t)40, fi1.compressedSize);
    CPPUNIT_ASSERT_EQUAL((size_t)130, fi1.uncompressedSize);

    FileInfo& fi2 = vec->at(1);
    CPPUNIT_ASSERT_EQUAL(String("rootfile2.txt"), fi2.filename);
    CPPUNIT_ASSERT_EQUAL(StringUtil::BLANK, fi2.path);
    CPPUNIT_ASSERT_EQUAL((size_t)45, fi2.compressedSize);
    CPPUNIT_ASSERT_EQUAL((size_t)156, fi2.uncompressedSize);
}
void ZipArchiveTests::testFindFileInfoRecursive()
{
    ZipArchive arch(testPath, "Zip");
    arch.load();
    FileInfoListPtr vec = arch.findFileInfo("*.material", true);

    CPPUNIT_ASSERT_EQUAL((size_t)4, vec->size());

    FileInfo& fi3 = vec->at(0);
    CPPUNIT_ASSERT_EQUAL(String("file.material"), fi3.filename);
    CPPUNIT_ASSERT_EQUAL(String("level1/materials/scripts/"), fi3.path);
    CPPUNIT_ASSERT_EQUAL((size_t)0, fi3.compressedSize);
    CPPUNIT_ASSERT_EQUAL((size_t)0, fi3.uncompressedSize);

    FileInfo& fi4 = vec->at(1);
    CPPUNIT_ASSERT_EQUAL(String("file2.material"), fi4.filename);
    CPPUNIT_ASSERT_EQUAL(String("level1/materials/scripts/"), fi4.path);
    CPPUNIT_ASSERT_EQUAL((size_t)0, fi4.compressedSize);
    CPPUNIT_ASSERT_EQUAL((size_t)0, fi4.uncompressedSize);

    FileInfo& fi5 = vec->at(2);
    CPPUNIT_ASSERT_EQUAL(String("file3.material"), fi5.filename);
    CPPUNIT_ASSERT_EQUAL(String("level2/materials/scripts/"), fi5.path);
    CPPUNIT_ASSERT_EQUAL((size_t)0, fi5.compressedSize);
    CPPUNIT_ASSERT_EQUAL((size_t)0, fi5.uncompressedSize);

    FileInfo& fi6 = vec->at(3);
    CPPUNIT_ASSERT_EQUAL(String("file4.material"), fi6.filename);
    CPPUNIT_ASSERT_EQUAL(String("level2/materials/scripts/"), fi6.path);
    CPPUNIT_ASSERT_EQUAL((size_t)0, fi6.compressedSize);
    CPPUNIT_ASSERT_EQUAL((size_t)0, fi6.uncompressedSize);
}
void ZipArchiveTests::testFileRead()
{
    ZipArchive arch(testPath, "Zip");
    arch.load();

    DataStreamPtr stream = arch.open("rootfile.txt");
    CPPUNIT_ASSERT_EQUAL(String("this is line 1 in file 1"), stream->getLine());
    CPPUNIT_ASSERT_EQUAL(String("this is line 2 in file 1"), stream->getLine());
    CPPUNIT_ASSERT_EQUAL(String("this is line 3 in file 1"), stream->getLine());
    CPPUNIT_ASSERT_EQUAL(String("this is line 4 in file 1"), stream->getLine());
    CPPUNIT_ASSERT_EQUAL(String("this is line 5 in file 1"), stream->getLine());
    CPPUNIT_ASSERT(stream->eof());

}
void ZipArchiveTests::testReadInterleave()
{
    // Test overlapping reads from same archive

    ZipArchive arch(testPath, "Zip");
    arch.load();

    // File 1
    DataStreamPtr stream1 = arch.open("rootfile.txt");
    CPPUNIT_ASSERT_EQUAL(String("this is line 1 in file 1"), stream1->getLine());
    CPPUNIT_ASSERT_EQUAL(String("this is line 2 in file 1"), stream1->getLine());

    // File 2
    DataStreamPtr stream2 = arch.open("rootfile2.txt");
    CPPUNIT_ASSERT_EQUAL(String("this is line 1 in file 2"), stream2->getLine());
    CPPUNIT_ASSERT_EQUAL(String("this is line 2 in file 2"), stream2->getLine());
    CPPUNIT_ASSERT_EQUAL(String("this is line 3 in file 2"), stream2->getLine());

    // File 1
    CPPUNIT_ASSERT_EQUAL(String("this is line 3 in file 1"), stream1->getLine());
    CPPUNIT_ASSERT_EQUAL(String("this is line 4 in file 1"), stream1->getLine());
    CPPUNIT_ASSERT_EQUAL(String("this is line 5 in file 1"), stream1->getLine());
    CPPUNIT_ASSERT(stream1->eof());

    // File 2
    CPPUNIT_ASSERT_EQUAL(String("this is line 4 in file 2"), stream2->getLine());
    CPPUNIT_ASSERT_EQUAL(String("this is line 5 in file 2"), stream2->getLine());
    CPPUNIT_ASSERT_EQUAL(String("this is line 6 in file 2"), stream2->getLine());
    CPPUNIT_ASSERT(stream2->eof());

}
<commit_msg>Add fallback path for ZipArchiveTests.<commit_after>/*
-----------------------------------------------------------------------------
This source file is part of OGRE
(Object-oriented Graphics Rendering Engine)
For the latest info, see http://www.ogre3d.org/

Copyright (c) 2000-2013 Torus Knot Software Ltd

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
-----------------------------------------------------------------------------
*/
#include "ZipArchiveTests.h"
#include "OgreZip.h"

#if OGRE_PLATFORM == OGRE_PLATFORM_APPLE
#include "macUtils.h"
#endif

using namespace Ogre;

// Register the suite
CPPUNIT_TEST_SUITE_REGISTRATION( ZipArchiveTests );

void ZipArchiveTests::setUp()
{
#if OGRE_PLATFORM == OGRE_PLATFORM_APPLE
    testPath = macBundlePath() + "/Contents/Resources/Media/misc/ArchiveTest.zip";
#elif OGRE_PLATFORM == OGRE_PLATFORM_WIN32
    testPath = "./Tests/OgreMain/misc/ArchiveTest.zip";
#else
    testPath = "../Tests/OgreMain/misc/ArchiveTest.zip";
#endif
}
void ZipArchiveTests::tearDown()
{
}

void ZipArchiveTests::testListNonRecursive()
{
    ZipArchive arch(testPath, "Zip");
	try {
    arch.load();
	} catch (Ogre::Exception e) {
		// If it starts in build/bin/debug
		arch = ZipArchive("../../../" + testPath, "Zip");
		arch.load();
	}
    StringVectorPtr vec = arch.list(false);

    CPPUNIT_ASSERT_EQUAL((size_t)2, vec->size());
    CPPUNIT_ASSERT_EQUAL(String("rootfile.txt"), vec->at(0));
    CPPUNIT_ASSERT_EQUAL(String("rootfile2.txt"), vec->at(1));

}
void ZipArchiveTests::testListRecursive()
{
    ZipArchive arch(testPath, "Zip");
	try {
    arch.load();
	}
	catch (Ogre::Exception e) {
		// If it starts in build/bin/debug
		arch = ZipArchive("../../../" + testPath, "Zip");
		arch.load();
	}
    StringVectorPtr vec = arch.list(true);

    CPPUNIT_ASSERT_EQUAL((size_t)6, vec->size());
    CPPUNIT_ASSERT_EQUAL(String("file.material"), vec->at(0));
    CPPUNIT_ASSERT_EQUAL(String("file2.material"), vec->at(1));
    CPPUNIT_ASSERT_EQUAL(String("file3.material"), vec->at(2));
    CPPUNIT_ASSERT_EQUAL(String("file4.material"), vec->at(3));
    CPPUNIT_ASSERT_EQUAL(String("rootfile.txt"), vec->at(4));
    CPPUNIT_ASSERT_EQUAL(String("rootfile2.txt"), vec->at(5));
}
void ZipArchiveTests::testListFileInfoNonRecursive()
{
    ZipArchive arch(testPath, "Zip");
	try {
    arch.load();
	}
	catch (Ogre::Exception e) {
		// If it starts in build/bin/debug
		arch = ZipArchive("../../../" + testPath, "Zip");
		arch.load();
	}
    FileInfoListPtr vec = arch.listFileInfo(false);

    CPPUNIT_ASSERT_EQUAL((size_t)2, vec->size());
    FileInfo& fi1 = vec->at(0);
    CPPUNIT_ASSERT_EQUAL(String("rootfile.txt"), fi1.filename);
    CPPUNIT_ASSERT_EQUAL(StringUtil::BLANK, fi1.path);
    CPPUNIT_ASSERT_EQUAL((size_t)40, fi1.compressedSize);
    CPPUNIT_ASSERT_EQUAL((size_t)130, fi1.uncompressedSize);

    FileInfo& fi2 = vec->at(1);
    CPPUNIT_ASSERT_EQUAL(String("rootfile2.txt"), fi2.filename);
    CPPUNIT_ASSERT_EQUAL(StringUtil::BLANK, fi2.path);
    CPPUNIT_ASSERT_EQUAL((size_t)45, fi2.compressedSize);
    CPPUNIT_ASSERT_EQUAL((size_t)156, fi2.uncompressedSize);
}
void ZipArchiveTests::testListFileInfoRecursive()
{
    ZipArchive arch(testPath, "Zip");
	try {
    arch.load();
	}
	catch (Ogre::Exception e) {
		// If it starts in build/bin/debug
		arch = ZipArchive("../../../" + testPath, "Zip");
		arch.load();
	}
    FileInfoListPtr vec = arch.listFileInfo(true);

    CPPUNIT_ASSERT_EQUAL((size_t)6, vec->size());
    FileInfo& fi3 = vec->at(0);
    CPPUNIT_ASSERT_EQUAL(String("file.material"), fi3.filename);
    CPPUNIT_ASSERT_EQUAL(String("level1/materials/scripts/"), fi3.path);
    CPPUNIT_ASSERT_EQUAL((size_t)0, fi3.compressedSize);
    CPPUNIT_ASSERT_EQUAL((size_t)0, fi3.uncompressedSize);

    FileInfo& fi4 = vec->at(1);
    CPPUNIT_ASSERT_EQUAL(String("file2.material"), fi4.filename);
    CPPUNIT_ASSERT_EQUAL(String("level1/materials/scripts/"), fi4.path);
    CPPUNIT_ASSERT_EQUAL((size_t)0, fi4.compressedSize);
    CPPUNIT_ASSERT_EQUAL((size_t)0, fi4.uncompressedSize);

    FileInfo& fi5 = vec->at(2);
    CPPUNIT_ASSERT_EQUAL(String("file3.material"), fi5.filename);
    CPPUNIT_ASSERT_EQUAL(String("level2/materials/scripts/"), fi5.path);
    CPPUNIT_ASSERT_EQUAL((size_t)0, fi5.compressedSize);
    CPPUNIT_ASSERT_EQUAL((size_t)0, fi5.uncompressedSize);

    FileInfo& fi6 = vec->at(3);
    CPPUNIT_ASSERT_EQUAL(String("file4.material"), fi6.filename);
    CPPUNIT_ASSERT_EQUAL(String("level2/materials/scripts/"), fi6.path);
    CPPUNIT_ASSERT_EQUAL((size_t)0, fi6.compressedSize);
    CPPUNIT_ASSERT_EQUAL((size_t)0, fi6.uncompressedSize);

    FileInfo& fi1 = vec->at(4);
    CPPUNIT_ASSERT_EQUAL(String("rootfile.txt"), fi1.filename);
    CPPUNIT_ASSERT_EQUAL(StringUtil::BLANK, fi1.path);
    CPPUNIT_ASSERT_EQUAL((size_t)40, fi1.compressedSize);
    CPPUNIT_ASSERT_EQUAL((size_t)130, fi1.uncompressedSize);

    FileInfo& fi2 = vec->at(5);
    CPPUNIT_ASSERT_EQUAL(String("rootfile2.txt"), fi2.filename);
    CPPUNIT_ASSERT_EQUAL(StringUtil::BLANK, fi2.path);
    CPPUNIT_ASSERT_EQUAL((size_t)45, fi2.compressedSize);
    CPPUNIT_ASSERT_EQUAL((size_t)156, fi2.uncompressedSize);

}
void ZipArchiveTests::testFindNonRecursive()
{
    ZipArchive arch(testPath, "Zip");
	try {
    arch.load();
	}
	catch (Ogre::Exception e) {
		// If it starts in build/bin/debug
		arch = ZipArchive("../../../" + testPath, "Zip");
		arch.load();
	}
    StringVectorPtr vec = arch.find("*.txt", false);

    CPPUNIT_ASSERT_EQUAL((size_t)2, vec->size());
    CPPUNIT_ASSERT_EQUAL(String("rootfile.txt"), vec->at(0));
    CPPUNIT_ASSERT_EQUAL(String("rootfile2.txt"), vec->at(1));
}
void ZipArchiveTests::testFindRecursive()
{
    ZipArchive arch(testPath, "Zip");
	try {
    arch.load();
	}
	catch (Ogre::Exception e) {
		// If it starts in build/bin/debug
		arch = ZipArchive("../../../" + testPath, "Zip");
		arch.load();
	}
    StringVectorPtr vec = arch.find("*.material", true);

    CPPUNIT_ASSERT_EQUAL((size_t)4, vec->size());
    CPPUNIT_ASSERT_EQUAL(String("file.material"), vec->at(0));
    CPPUNIT_ASSERT_EQUAL(String("file2.material"), vec->at(1));
    CPPUNIT_ASSERT_EQUAL(String("file3.material"), vec->at(2));
    CPPUNIT_ASSERT_EQUAL(String("file4.material"), vec->at(3));
}
void ZipArchiveTests::testFindFileInfoNonRecursive()
{
    ZipArchive arch(testPath, "Zip");
	try {
    arch.load();
	}
	catch (Ogre::Exception e) {
		// If it starts in build/bin/debug
		arch = ZipArchive("../../../" + testPath, "Zip");
		arch.load();
	}
    FileInfoListPtr vec = arch.findFileInfo("*.txt", false);

    CPPUNIT_ASSERT_EQUAL((size_t)2, vec->size());
    FileInfo& fi1 = vec->at(0);
    CPPUNIT_ASSERT_EQUAL(String("rootfile.txt"), fi1.filename);
    CPPUNIT_ASSERT_EQUAL(StringUtil::BLANK, fi1.path);
    CPPUNIT_ASSERT_EQUAL((size_t)40, fi1.compressedSize);
    CPPUNIT_ASSERT_EQUAL((size_t)130, fi1.uncompressedSize);

    FileInfo& fi2 = vec->at(1);
    CPPUNIT_ASSERT_EQUAL(String("rootfile2.txt"), fi2.filename);
    CPPUNIT_ASSERT_EQUAL(StringUtil::BLANK, fi2.path);
    CPPUNIT_ASSERT_EQUAL((size_t)45, fi2.compressedSize);
    CPPUNIT_ASSERT_EQUAL((size_t)156, fi2.uncompressedSize);
}
void ZipArchiveTests::testFindFileInfoRecursive()
{
    ZipArchive arch(testPath, "Zip");
	try {
    arch.load();
	}
	catch (Ogre::Exception e) {
		// If it starts in build/bin/debug
		arch = ZipArchive("../../../" + testPath, "Zip");
		arch.load();
	}
    FileInfoListPtr vec = arch.findFileInfo("*.material", true);

    CPPUNIT_ASSERT_EQUAL((size_t)4, vec->size());

    FileInfo& fi3 = vec->at(0);
    CPPUNIT_ASSERT_EQUAL(String("file.material"), fi3.filename);
    CPPUNIT_ASSERT_EQUAL(String("level1/materials/scripts/"), fi3.path);
    CPPUNIT_ASSERT_EQUAL((size_t)0, fi3.compressedSize);
    CPPUNIT_ASSERT_EQUAL((size_t)0, fi3.uncompressedSize);

    FileInfo& fi4 = vec->at(1);
    CPPUNIT_ASSERT_EQUAL(String("file2.material"), fi4.filename);
    CPPUNIT_ASSERT_EQUAL(String("level1/materials/scripts/"), fi4.path);
    CPPUNIT_ASSERT_EQUAL((size_t)0, fi4.compressedSize);
    CPPUNIT_ASSERT_EQUAL((size_t)0, fi4.uncompressedSize);

    FileInfo& fi5 = vec->at(2);
    CPPUNIT_ASSERT_EQUAL(String("file3.material"), fi5.filename);
    CPPUNIT_ASSERT_EQUAL(String("level2/materials/scripts/"), fi5.path);
    CPPUNIT_ASSERT_EQUAL((size_t)0, fi5.compressedSize);
    CPPUNIT_ASSERT_EQUAL((size_t)0, fi5.uncompressedSize);

    FileInfo& fi6 = vec->at(3);
    CPPUNIT_ASSERT_EQUAL(String("file4.material"), fi6.filename);
    CPPUNIT_ASSERT_EQUAL(String("level2/materials/scripts/"), fi6.path);
    CPPUNIT_ASSERT_EQUAL((size_t)0, fi6.compressedSize);
    CPPUNIT_ASSERT_EQUAL((size_t)0, fi6.uncompressedSize);
}
void ZipArchiveTests::testFileRead()
{
    ZipArchive arch(testPath, "Zip");
	try {
    arch.load();
	}
	catch (Ogre::Exception e) {
		// If it starts in build/bin/debug
		arch = ZipArchive("../../../" + testPath, "Zip");
		arch.load();
	}

    DataStreamPtr stream = arch.open("rootfile.txt");
    CPPUNIT_ASSERT_EQUAL(String("this is line 1 in file 1"), stream->getLine());
    CPPUNIT_ASSERT_EQUAL(String("this is line 2 in file 1"), stream->getLine());
    CPPUNIT_ASSERT_EQUAL(String("this is line 3 in file 1"), stream->getLine());
    CPPUNIT_ASSERT_EQUAL(String("this is line 4 in file 1"), stream->getLine());
    CPPUNIT_ASSERT_EQUAL(String("this is line 5 in file 1"), stream->getLine());
    CPPUNIT_ASSERT(stream->eof());

}
void ZipArchiveTests::testReadInterleave()
{
    // Test overlapping reads from same archive

    ZipArchive arch(testPath, "Zip");
	try {
    arch.load();
	}
	catch (Ogre::Exception e) {
		// If it starts in build/bin/debug
		arch = ZipArchive("../../../" + testPath, "Zip");
		arch.load();
	}

    // File 1
    DataStreamPtr stream1 = arch.open("rootfile.txt");
    CPPUNIT_ASSERT_EQUAL(String("this is line 1 in file 1"), stream1->getLine());
    CPPUNIT_ASSERT_EQUAL(String("this is line 2 in file 1"), stream1->getLine());

    // File 2
    DataStreamPtr stream2 = arch.open("rootfile2.txt");
    CPPUNIT_ASSERT_EQUAL(String("this is line 1 in file 2"), stream2->getLine());
    CPPUNIT_ASSERT_EQUAL(String("this is line 2 in file 2"), stream2->getLine());
    CPPUNIT_ASSERT_EQUAL(String("this is line 3 in file 2"), stream2->getLine());

    // File 1
    CPPUNIT_ASSERT_EQUAL(String("this is line 3 in file 1"), stream1->getLine());
    CPPUNIT_ASSERT_EQUAL(String("this is line 4 in file 1"), stream1->getLine());
    CPPUNIT_ASSERT_EQUAL(String("this is line 5 in file 1"), stream1->getLine());
    CPPUNIT_ASSERT(stream1->eof());

    // File 2
    CPPUNIT_ASSERT_EQUAL(String("this is line 4 in file 2"), stream2->getLine());
    CPPUNIT_ASSERT_EQUAL(String("this is line 5 in file 2"), stream2->getLine());
    CPPUNIT_ASSERT_EQUAL(String("this is line 6 in file 2"), stream2->getLine());
    CPPUNIT_ASSERT(stream2->eof());

}
<|endoftext|>