Subversion Repositories tpanel

Rev

Rev 126 | Blame | Last modification | View Log | RSS feed

/*
 * Copyright (C) 2021 by Andreas Theofilu <andreas@theosys.at>
 *
 * This program 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.
 *
 * This program 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 this program; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 */

#include <iostream>
#include <fstream>
#include <functional>

#include <stdio.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <unistd.h>

#include <QFile>
#ifdef __ANDROID__
#include <QtAndroid>
#endif
#include <QMap>
#include <QHash>

#include "ttpinit.h"
#include "terror.h"
#include "tvalidatefile.h"
#include "tconfig.h"
#include "tfsfreader.h"
#include "tdirectory.h"
#include "tresources.h"

#if __cplusplus < 201402L
#   error "This module requires at least C++14 standard!"
#else
#   if __cplusplus < 201703L
#       include <experimental/filesystem>
        namespace fs = std::experimental::filesystem;
#       warning "Support for C++14 and experimental filesystem will be removed in a future version!"
#   else
#       include <filesystem>
#       ifdef __ANDROID__
            namespace fs = std::__fs::filesystem;
#       else
            namespace fs = std::filesystem;
#       endif
#   endif
#endif

using std::string;
using std::vector;
using std::ostream;
using std::bind;
using std::ifstream;

#define SYSTEM_DEFAULT      "/.system"

TTPInit::TTPInit()
{
    DECL_TRACER("TTPInit::TTPInit()");
}

TTPInit::TTPInit(const string& path)
    : mPath(path)
{
    DECL_TRACER("TTPInit::TTPInit(const string& path)")

    createDirectoryStructure();
    createPanelConfigs();
    loadSurfaceFromController();
}

bool TTPInit::createPanelConfigs()
{
    DECL_TRACER("TTPInit::createPanelConfigs()");

    vector<string> resFiles = {
        ":ressources/external.xma",
        ":ressources/fnt.xma",
        ":ressources/icon.xma",
        ":ressources/_main.xml",
        ":ressources/manifest.xma",
        ":ressources/map.xma",
        ":ressources/pal_001.xma",
        ":ressources/prj.xma",
        ":ressources/_setup.xml",
        ":ressources/table.xma",
        ":ressources/fonts/arial.ttf",
        ":ressources/images/theosys_logo.png",
        ":ressources/__system/graphics/fonts/amxbold_.ttf",
        ":ressources/__system/graphics/fonts/arialbd.ttf",
        ":ressources/__system/graphics/fonts/arial.ttf",
        ":ressources/__system/graphics/fonts/cour.ttf",
        ":ressources/__system/graphics/sounds/audioTest.wav",
        ":ressources/__system/graphics/sounds/docked.mp3",
        ":ressources/__system/graphics/sounds/doubleBeep01.wav",
        ":ressources/__system/graphics/sounds/doubleBeep02.wav",
        ":ressources/__system/graphics/sounds/doubleBeep03.wav",
        ":ressources/__system/graphics/sounds/doubleBeep04.wav",
        ":ressources/__system/graphics/sounds/doubleBeep05.wav",
        ":ressources/__system/graphics/sounds/doubleBeep06.wav",
        ":ressources/__system/graphics/sounds/doubleBeep07.wav",
        ":ressources/__system/graphics/sounds/doubleBeep08.wav",
        ":ressources/__system/graphics/sounds/doubleBeep09.wav",
        ":ressources/__system/graphics/sounds/doubleBeep10.wav",
        ":ressources/__system/graphics/sounds/doubleBeep.wav",
        ":ressources/__system/graphics/sounds/ringback.wav",
        ":ressources/__system/graphics/sounds/ringtone.wav",
        ":ressources/__system/graphics/sounds/singleBeep01.wav",
        ":ressources/__system/graphics/sounds/singleBeep02.wav",
        ":ressources/__system/graphics/sounds/singleBeep03.wav",
        ":ressources/__system/graphics/sounds/singleBeep04.wav",
        ":ressources/__system/graphics/sounds/singleBeep05.wav",
        ":ressources/__system/graphics/sounds/singleBeep06.wav",
        ":ressources/__system/graphics/sounds/singleBeep07.wav",
        ":ressources/__system/graphics/sounds/singleBeep08.wav",
        ":ressources/__system/graphics/sounds/singleBeep09.wav",
        ":ressources/__system/graphics/sounds/singleBeep10.wav",
        ":ressources/__system/graphics/sounds/singleBeep.wav",
        ":ressources/__system/graphics/draw.xma",
        ":ressources/__system/graphics/fnt.xma",
        ":ressources/__system/graphics/version.xma"
    };

    bool err = false;
    vector<string>::iterator iter;

    for (iter = resFiles.begin(); iter != resFiles.end(); ++iter)
    {
        if (!copyFile(*iter))
            err = true;
    }

    // Mark files as system default files
    try
    {
        string marker = mPath + SYSTEM_DEFAULT;
        std::ofstream mark(marker);
        time_t t = time(NULL);
        mark.write((char *)&t, sizeof(time_t));
        mark.close();
    }
    catch (std::exception& e)
    {
        MSG_ERROR("Error creating a marker file: " << e.what());
        err = true;
    }

    return err;
}

bool TTPInit::createSystemConfigs()
{
    DECL_TRACER("TTPInit::createSystemConfigs()");

    vector<string> resFiles = {
        ":ressources/__system/graphics/fonts/amxbold_.ttf",
        ":ressources/__system/graphics/fonts/arialbd.ttf",
        ":ressources/__system/graphics/fonts/arial.ttf",
        ":ressources/__system/graphics/fonts/cour.ttf",
        ":ressources/__system/graphics/sounds/audioTest.wav",
        ":ressources/__system/graphics/sounds/docked.mp3",
        ":ressources/__system/graphics/sounds/doubleBeep01.wav",
        ":ressources/__system/graphics/sounds/doubleBeep02.wav",
        ":ressources/__system/graphics/sounds/doubleBeep03.wav",
        ":ressources/__system/graphics/sounds/doubleBeep04.wav",
        ":ressources/__system/graphics/sounds/doubleBeep05.wav",
        ":ressources/__system/graphics/sounds/doubleBeep06.wav",
        ":ressources/__system/graphics/sounds/doubleBeep07.wav",
        ":ressources/__system/graphics/sounds/doubleBeep08.wav",
        ":ressources/__system/graphics/sounds/doubleBeep09.wav",
        ":ressources/__system/graphics/sounds/doubleBeep10.wav",
        ":ressources/__system/graphics/sounds/doubleBeep.wav",
        ":ressources/__system/graphics/sounds/ringback.wav",
        ":ressources/__system/graphics/sounds/ringtone.wav",
        ":ressources/__system/graphics/sounds/singleBeep01.wav",
        ":ressources/__system/graphics/sounds/singleBeep02.wav",
        ":ressources/__system/graphics/sounds/singleBeep03.wav",
        ":ressources/__system/graphics/sounds/singleBeep04.wav",
        ":ressources/__system/graphics/sounds/singleBeep05.wav",
        ":ressources/__system/graphics/sounds/singleBeep06.wav",
        ":ressources/__system/graphics/sounds/singleBeep07.wav",
        ":ressources/__system/graphics/sounds/singleBeep08.wav",
        ":ressources/__system/graphics/sounds/singleBeep09.wav",
        ":ressources/__system/graphics/sounds/singleBeep10.wav",
        ":ressources/__system/graphics/sounds/singleBeep.wav",
        ":ressources/__system/graphics/draw.xma",
        ":ressources/__system/graphics/fnt.xma",
        ":ressources/__system/graphics/version.xma"
    };

    bool err = false;
    vector<string>::iterator iter;

    for (iter = resFiles.begin(); iter != resFiles.end(); ++iter)
    {
        if (!copyFile(*iter))
            err = true;
    }

    return err;
}

bool TTPInit::createDirectoryStructure()
{
    DECL_TRACER("TTPInit::createDirectoryStructure()");

    if (mPath.empty())
    {
        MSG_ERROR("Got no path to create the directory structure!");
        return false;
    }

    string pfad = mPath;

    if (!_makeDir(pfad))
        return false;

    pfad = mPath + "/fonts";

    if (!_makeDir(pfad))
        return false;

    pfad = mPath + "/images";

    if (!_makeDir(pfad))
        return false;

    pfad = mPath + "/sounds";

    if (!_makeDir(pfad))
        return false;

    pfad = mPath + "/__system";

    if (!_makeDir(pfad))
        return false;

    pfad = mPath + "/__system/graphics";

    if (!_makeDir(pfad))
        return false;

    pfad = mPath + "/__system/graphics/fonts";

    if (!_makeDir(pfad))
        return false;

    pfad = mPath + "/__system/graphics/images";

    if (!_makeDir(pfad))
        return false;

    pfad = mPath + "/__system/graphics/sounds";

    if (!_makeDir(pfad))
        return false;

    pfad = mPath + "/__system/graphics/cursors";

    if (!_makeDir(pfad))
        return false;

    pfad = mPath + "/__system/graphics/sliders";

    if (!_makeDir(pfad))
        return false;

    pfad = mPath + "/__system/graphics/borders";

    if (!_makeDir(pfad))
        return false;

    return true;
}

bool TTPInit::_makeDir(const std::string& dir)
{
    DECL_TRACER("TTPInit::_makeDir(const std::string& dir)");

    TValidateFile vf;

    if (!vf.isValidDir(dir))
    {
        if (mkdir (dir.c_str(), S_IRWXU | S_IRWXG | S_IRWXG) != 0)
        {
            MSG_ERROR("Directory " << dir << ": " << strerror(errno));
            return false;
        }
    }

    return true;
}

bool TTPInit::copyFile(const std::string& fname)
{
    DECL_TRACER("TTPInit::copyFile(const std::string& fname)");

    bool err = false;
    QFile external(fname.c_str());
    size_t pos = fname.find_first_of("/");
    string bname;

    if (pos != string::npos)
        bname = fname.substr(pos);
    else
        bname = fname;

    if (external.exists())
    {
        QString path = mPath.c_str();
        path += bname.c_str();
        bname = path.toStdString();

        // If the target already exists we must delete it first.
        if (access(bname.data(), F_OK) == 0)
            remove(bname.data());

        if (!external.copy(path))
        {
#ifdef __ANDROID__
            if (!askPermissions())
            {
                MSG_ERROR("Could not copy \"" << bname << "\" to " << path.toStdString() << " because permission was denied!");
                err = true;
            }
            else if (!external.copy(path))
            {
                MSG_ERROR("Could not copy \"" << bname << "\" to " << path.toStdString());
                err = true;
            }
#else
            MSG_ERROR("Could not copy \"" << bname << "\" to " << path.toStdString());
            err = true;
#endif
        }
    }
    else
    {
        MSG_ERROR("File " << external.fileName().toStdString() << " doesn't exist!");
        err = true;
    }

    return err;
}

string TTPInit::getTmpFileName()
{
    DECL_TRACER("TTPInit::getTmpFileName()");

    const string alphanum =
            "0123456789"
            "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
            "abcdefghijklmnopqrstuvwxyz";

    size_t stringLength = alphanum.length() - 1;
    std::string Str;
    char *tmp = getenv("TMP");

    if (!tmp)
        tmp = getenv("TEMP");

    if (!tmp)
        tmp = getenv("HOME");
    else
        tmp = (char *)"/tmp";

    Str.assign(tmp);
    Str.append("/");

    for(size_t i = 0; i < MAX_TMP_LEN; ++i)
        Str += alphanum[rand() % stringLength];

    // We create the file. YES, this is a security hole but in our case we have
    // no real alternative for now.
    try
    {
        std::ofstream tmpfile;
        tmpfile.open(Str, std::ofstream::out | std::ofstream::binary | std::ofstream::trunc);

        if (!tmpfile.is_open())
        {
            MSG_ERROR("Error opening a temporary file!");
        }
        else
            tmpfile.flush();

        tmpfile.close();
    }
    catch (std::exception& e)
    {
        MSG_ERROR("Couldn't create a temporary file: " << e.what());
        return string();
    }

    return Str;
}

/**
 * This methods checks if there exists a previous downloaded TP4 file. If this
 * is the case, nothing happens.
 * If there is no previous downloaded file it checks if there is one on the
 * controller and downloads it if it exists. After successfull download the
 * file is unpacked.
 *
 * @return TRUE is returned when there was a file successfully downloaded and
 * unpacked. In any other case FALSE is returned.
 */
bool TTPInit::loadSurfaceFromController(bool force)
{
    DECL_TRACER("TTPInit::loadSurfaceFromController(bool force)");

    TFsfReader reader;
    reader.regCallbackProgress(bind(&TTPInit::progressCallback, this, std::placeholders::_1));
    string target = mPath + "/" + TConfig::getFtpSurface();
    size_t pos = 0;

    if ((pos = mPath.find_last_of("/")) != string::npos)
        target = mPath.substr(0, pos) + "/" + TConfig::getFtpSurface();

    if (!force)
    {
        if (!isVirgin() || TConfig::getFtpDownloadTime() > 0)
            return false;
    }

    if (_processEvents)
        _processEvents();

    // To be sure the target directory tree is empty, we delete all files but
    // keep the system directories and their content, if they exist.
    dir::TDirectory dir;

    if (dir.exists(mPath))
    {
        dir.dropDir(mPath);
        dir.dropDir(mPath + "/fonts");
        dir.dropDir(mPath + "/images");
        dir.dropDir(mPath + "/sounds");
    }

    if (!reader.copyOverFTP(TConfig::getFtpSurface(), target))
    {
        if (TConfig::getFtpDownloadTime() == 0)
            createPanelConfigs();

        return false;
    }

    if (_processEvents)
        _processEvents();

    if (!reader.unpack(target, mPath))
    {
        MSG_ERROR("Unpacking was not successfull.");
        return false;
    }

    if (!force || !dir.exists(mPath + "/__system"))
    {
        createDirectoryStructure();
        createSystemConfigs();
    }

    if (_processEvents)
        _processEvents();

    dir.dropFile(target);       // We remove our traces
    dir.dropFile(mPath + SYSTEM_DEFAULT);   // No more system default files
    TConfig::saveFtpDownloadTime(time(NULL));
    TConfig::saveSettings();
    return true;
}

vector<string>& TTPInit::getFileList(const string& filter)
{
    DECL_TRACER("TTPInit::getFileList(const string& filter)");

    ftplib *ftp = new ftplib();
    ftp->regLogging(bind(&TTPInit::logging, this, std::placeholders::_1, std::placeholders::_2));

    if (TConfig::getFtpPassive())
        ftp->SetConnmode(ftplib::pasv);
    else
        ftp->SetConnmode(ftplib::port);

    string scon = TConfig::getController() + ":21";
    MSG_DEBUG("Trying to connect to " << scon);

    if (!ftp->Connect(scon.c_str()))
    {
        delete ftp;
        return mDirList;
    }

    string sUser = TConfig::getFtpUser();
    string sPass = TConfig::getFtpPassword();
    MSG_DEBUG("Trying to login <" << sUser << ", " << sPass << ">");

    if (!ftp->Login(sUser.c_str(), sPass.c_str()))
    {
        delete ftp;
        return mDirList;
    }

//    string tmpFile = std::tmpnam(nullptr);
    string tmpFile = getTmpFileName();
    MSG_DEBUG("Reading remote directory / into file " << tmpFile);
    ftp->Nlst(tmpFile.c_str(), "/");
    ftp->Quit();
    delete ftp;
    mDirList.clear();

    try
    {
        char buffer[1024];
        string uFilter = toUpper((std::string&)filter);

        std::ifstream ifile(tmpFile);

        while (ifile.getline(buffer, sizeof(buffer)))
        {
            string buf = buffer;
            string fname = trim(buf);
            MSG_DEBUG("FTP line: " << buf << " (" << fname << ")");

            if (!filter.empty())
            {
                if (endsWith(toUpper(buf), uFilter))
                    mDirList.push_back(trim(fname));
            }
            else
                mDirList.push_back(trim(fname));
        }

        ifile.close();
    }
    catch (std::exception& e)
    {
        MSG_ERROR("Error opening file " << tmpFile << ": " << e.what());
    }

    fs::remove(tmpFile);

    if (mDirList.size() > 0)
    {
        vector<string>::iterator iter;

        for (iter = mDirList.begin(); iter != mDirList.end(); ++iter)
        {
            MSG_DEBUG("File: " << *iter);
        }
    }

    return mDirList;
}

int TTPInit::progressCallback(off64_t xfer)
{
    DECL_TRACER("TTPInit::progressCallback(off64_t xfer)");

    if (_processEvents && xfer > 0)
        _processEvents();

    return 1;
}

bool TTPInit::isSystemDefault()
{
    DECL_TRACER("TTPInit::isSystemDefault()");

    try
    {
        string marker = mPath + SYSTEM_DEFAULT;
        return fs::exists(marker);
    }
    catch (std::exception& e)
    {
        MSG_ERROR("File system error: " << e.what())
        return false;
    }

    return true;
}

bool TTPInit::isVirgin()
{
    DECL_TRACER("TTPInit::isVirgin()");

    try
    {
        if (!fs::exists(mPath) || isSystemDefault())
            return true;
    }
    catch (std::exception& e)
    {
        MSG_ERROR("File system error: " << e.what());
        return true;
    }

    return false;
}

void TTPInit::logging(int level, const std::string &msg)
{
    switch(level)
    {
        case LOG_INFO:      MSG_INFO(msg); break;
        case LOG_WARNING:   MSG_WARNING(msg); break;
        case LOG_ERROR:     MSG_ERROR(msg); break;
        case LOG_TRACE:     MSG_TRACE(msg); break;
        case LOG_DEBUG:     MSG_DEBUG(msg); break;
    }
}

#ifdef __ANDROID__
bool TTPInit::askPermissions()
{
    DECL_TRACER("TTPInit::askPermissions()");

    QStringList permissions = { "android.permission.READ_EXTERNAL_STORAGE", "android.permission.WRITE_EXTERNAL_STORAGE" };
    QtAndroid::PermissionResultMap perms = QtAndroid::requestPermissionsSync(permissions);

    for (auto iter = perms.begin(); iter != perms.end(); ++iter)
    {
        if (iter.value() == QtAndroid::PermissionResult::Denied)
            return false;
    }

    return true;
}
#endif