15.1.568. tablet_qt/tasklib/taskfactory.cpp

/*
    Copyright (C) 2012, University of Cambridge, Department of Psychiatry.
    Created by Rudolf Cardinal (rnc1001@cam.ac.uk).

    This file is part of CamCOPS.

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

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

    You should have received a copy of the GNU General Public License
    along with CamCOPS. If not, see <https://www.gnu.org/licenses/>.
*/

// #define DEBUG_TASK_CREATION

#include "taskfactory.h"
#include <algorithm>
#include "core/camcopsapp.h"
#include "db/databasemanager.h"
#include "lib/comparers.h"
#include "lib/version.h"
#include "tasklib/task.h"
#include "tasklib/tasksorter.h"
#include "version/camcopsversion.h"


// ===========================================================================
// TaskFactory
// ===========================================================================

TaskFactory::TaskFactory(CamcopsApp& app) :
    m_app(app)
{
}


void TaskFactory::registerTask(ProxyType proxy)
{
    m_initial_proxy_list.append(proxy);
    // We are here from WITHIN A CONSTRUCTOR (TaskProxy::TaskProxy), so don't
    // call back to the proxy.
}


void TaskFactory::finishRegistration()
{
    for (const ProxyType& proxy : m_initial_proxy_list) {
        TaskPtr p_task = proxy->create(m_app, m_app.db());
        TaskCache cache;
        cache.tablename = p_task->tablename();
        cache.shortname = p_task->shortname();
        cache.longname = p_task->longname();
        cache.anonymous = p_task->isAnonymous();
        cache.alltables = p_task->allTables();
        cache.proxy = proxy;
        if (m_map.contains(cache.tablename)) {
            QString msg = QString(
                "BAD TASK REGISTRATION: table %1 being registered for a second"
                " time by task with longname %2").arg(
                    cache.tablename, cache.longname);
            qFatal("%s", qPrintable(msg));
        }
        m_map.insert(cache.tablename, cache);  // tablenames are the keys
        m_tablenames.append(cache.tablename);
        m_all_tablenames += cache.alltables;
    }
    m_tablenames.sort();
    m_all_tablenames.sort();
}


QStringList TaskFactory::tablenames(const TaskClassSortMethod sort_method) const
{
    if (sort_method == TaskClassSortMethod::Tablename) {
        // Already sorted by this
        return m_tablenames;
    }
    using StringPair = QPair<QString, QString>;
    QVector<StringPair> pairs;
    const bool use_shortname = sort_method == TaskClassSortMethod::Shortname;
    for (const QString& tablename : m_tablenames) {
        const TaskCache& cache = m_map[tablename];
        pairs.append(StringPair(tablename, use_shortname ? cache.shortname
                                                         : cache.longname));
    }
    std::sort(pairs.begin(), pairs.end(), QPairSecondComparer());
    QStringList sorted_tablenames;
    for (const StringPair& pair : pairs) {
        sorted_tablenames.append(pair.first);
    }
    return sorted_tablenames;
}


QStringList TaskFactory::allTablenames() const
{
    return m_all_tablenames;
}


TaskPtr TaskFactory::create(const QString& key, const int load_pk) const
{
    if (!m_map.contains(key)) {
        qWarning().nospace() << "TaskFactory::create(" << key << ", "
                             << load_pk << ")" << "... no such task class";
        return TaskPtr(nullptr);
    }
#ifdef DEBUG_TASK_CREATION
    qDebug().nospace() << "TaskFactory::create(" << key << ", "
                       << load_pk << ")";
#endif
    ProxyType proxy = m_map[key].proxy;
    return proxy->create(m_app, m_app.db(), load_pk);
}


void TaskFactory::makeAllTables() const
{
    MapIteratorType it(m_map);
    while (it.hasNext()) {
        it.next();
        ProxyType proxy = it.value().proxy;
        TaskPtr p_task = proxy->create(m_app, m_app.db());
        p_task->makeTables();
    }
}


QString TaskFactory::shortname(const QString& key) const
{
    if (!m_map.contains(key)) {
        qWarning() << "Bad task: " << key;
        return nullptr;
    }
    return m_map[key].shortname;
}


QString TaskFactory::longname(const QString& key) const
{
    if (!m_map.contains(key)) {
        qWarning() << "Bad task: " << key;
        return nullptr;
    }
    return m_map[key].longname;
}


void TaskFactory::makeTables(const QString& key) const
{
    TaskPtr p_task = create(key);
    if (!p_task) {
        return;
    }
    p_task->makeTables();
}


TaskPtrList TaskFactory::fetchTasks(const QString& tablename, const bool sort) const
{
    // KEY SECURITY DECISIONS IMPLEMENTED HERE: which tasks users can see.
    const int patient_id = m_app.selectedPatientId();
    const bool patient_selected = patient_id != dbconst::NONEXISTENT_PK;
    TaskPtrList tasklist;
    if (tablename.isEmpty()) {
        // Patient summary view; "all tasks" request.
        // - Patient selected -> all tasks for current patient (whether locked
        //   or not).
        // - No patient selected -> return nothing.
        if (patient_selected) {
            tasklist = fetchAllTasksForPatient(patient_id);
        }
    } else if (!m_map.contains(tablename)) {
        // Duff task
        qWarning() << "Bad task: " << tablename;
    } else {
        // Specific task
        // - Patient-based task / patient selected -> tasks for that patient
        //   (whether locked or not).
        // - Patient-based task / no patient selected / unlocked -> all such
        //   tasks, for all patients.
        // - Patient-based task / no patient selected / locked -> nothing.
        // - Anonymous task / patient selected -> all such tasks
        //   ... see also TaskRegistrar::fetch().
        //   ... if you choose "none", users will probably wonder where
        //       tasks are vanishing to
        // - Anonymous task / no patient selected -> all such tasks
        const TaskCache& cache = m_map[tablename];
        ProxyType proxy = cache.proxy;
        const bool anonymous = cache.anonymous;
        const bool locked = m_app.locked();
        if (anonymous) {
            tasklist = proxy->fetch(m_app, m_app.db(), dbconst::NONEXISTENT_PK);
        } else {
            if (patient_selected || !locked) {
                tasklist = proxy->fetch(m_app, m_app.db(), patient_id);
            }
        }
    }

    if (sort) {
        // qDebug() << "Starting sort...";
        std::sort(tasklist.begin(), tasklist.end(), TaskSorter());
        // qDebug() << "... finished sort";
    }

    return tasklist;
}


bool TaskFactory::anyTasksPresent() const
{
    DatabaseManager& db = m_app.db();
    for (const QString& tablename : m_tablenames) {  // all task base tables
        if (db.count(tablename) > 0) {
            return true;
        }
    }
    return false;
}


TaskPtrList TaskFactory::fetchAllTasksForPatient(const int patient_id) const
{
    TaskPtrList tasklist;
    MapIteratorType it(m_map);
    while (it.hasNext()) {
        it.next();
        const TaskCache& cache = it.value();
        const bool anonymous = cache.anonymous;
        if (anonymous) {
            continue;
        }
        ProxyType proxy = cache.proxy;
        tasklist += proxy->fetch(m_app, m_app.db(), patient_id);
    }
    return tasklist;
}


TaskPtrList TaskFactory::allSpecimens() const
{
    TaskPtrList specimens;
    MapIteratorType it(m_map);
    while (it.hasNext()) {
        it.next();
        const TaskCache& cache = it.value();
        ProxyType proxy = cache.proxy;
        TaskPtr specimen = proxy->create(m_app, m_app.db(),
                                         dbconst::NONEXISTENT_PK);
        specimens.append(specimen);
    }
    return specimens;
}


TaskPtrList TaskFactory::allSpecimensExceptAnonymous() const
{
    TaskPtrList specimens;
    MapIteratorType it(m_map);
    while (it.hasNext()) {
        it.next();
        const TaskCache& cache = it.value();
        const bool anonymous = cache.anonymous;
        if (anonymous) {
            continue;
        }
        ProxyType proxy = cache.proxy;
        TaskPtr specimen = proxy->create(m_app, m_app.db(),
                                         dbconst::NONEXISTENT_PK);
        specimens.append(specimen);
    }
    return specimens;
}


void TaskFactory::upgradeDatabase(const Version& old_version,
                                  const Version& new_version)
{
    TaskPtrList specimens = allSpecimens();
    for (const TaskPtr& t : specimens) {
        t->upgradeDatabase(old_version, new_version);
    }
}


Version TaskFactory::minimumServerVersion(const QString& tablename) const
{
    // For speed order:
    if (m_map.contains(tablename)) {
        // It's a main tablename.
        TaskPtr specimen = create(tablename);
        return specimen->minimumServerVersion();
    }
    if (!m_all_tablenames.contains(tablename)) {
        // It's duff.
        qWarning() << "TaskFactory::minimumServerVersion: don't know table"
                   << tablename;
        return camcopsversion::MINIMUM_SERVER_VERSION;
    }
    // Otherwise, it's an ancillary.
    TaskPtrList specimens = allSpecimens();
    for (const TaskPtr& specimen : specimens) {
        const QStringList task_tables = specimen->allTables();
        if (task_tables.contains(tablename)) {
            return specimen->minimumServerVersion();
        }
    }
    qCritical() << "Bug in TaskFactory::minimumServerVersion! Tablename was"
                << tablename;
    return camcopsversion::MINIMUM_SERVER_VERSION;
}


QTextStream& operator<<(QTextStream& stream, const TaskFactory& f)
{
    const TaskPtrList specimens = f.allSpecimens();
    for (const TaskPtr& t : specimens) {
        stream
            << t->tablename()
            << ": "
            << t->shortname()
            << ", "
            << t->longname()
            << Qt::endl;
    }
    // Don't bother with reporting the numbers.
    // The user can use ./camcops --print_tasks 2>/dev/null | wc
    // and this way is more aligned to the Unix philosophy.
    return stream;
}