C

Qt Quick Ultralite fileloading Example

// Copyright (C) 2024 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial

#include "posixfilesystem.h"
#include <sys/stat.h>
#include <fcntl.h>
#include <cstring>
#include <string>
#include <filesystem>
#include <platforminterface/allocator.h>

#if defined(QUL_OS_LINUX)
#include <unistd.h>
#endif
#ifdef QUL_OS_MACOS
#include <unistd.h>
#define fstat64 fstat
#define stat64 stat
#endif
#ifdef QUL_OS_WIN
#include <io.h>
#include <windows.h>
#ifdef QUL_CC_MSVC
#define fstat64 _fstat64
#define stat64 __stat64
typedef uintptr_t ssize_t;
#endif
#endif

static std::string appFilePath()
{
#if defined(QUL_OS_LINUX) || defined(QUL_OS_MACOS)
    std::filesystem::path executable(std::string("/proc/") + std::to_string(::getpid()) + std::string("/exe"));

    if (!std::filesystem::exists(executable) || !std::filesystem::is_symlink(executable)) {
        return std::string();
    }
    executable = std::filesystem::canonical(executable);
    if (!std::filesystem::exists(executable))
        return std::string();

    return executable.remove_filename().string();
#elif defined(QUL_OS_WIN)
    wchar_t buffer[MAX_PATH + 2];
    DWORD v = GetModuleFileNameW(NULL, buffer, MAX_PATH + 1);
    buffer[MAX_PATH + 1] = 0;
    std::filesystem::path executable;

    if (v == 0)
        return NULL;
    else if (v <= MAX_PATH) {
        std::wstring ws(buffer);
        executable = std::filesystem::path(std::string(ws.begin(), ws.end()));
        return executable.remove_filename().string();
    }

    // MAX_PATH sized buffer wasn't large enough to contain the full path, use heap
    wchar_t *b = 0;
    int i = 1;
    size_t size;
    do {
        ++i;
        size = MAX_PATH * i;
        b = reinterpret_cast<wchar_t *>(std::realloc(b, (size + 1) * sizeof(wchar_t)));
        if (b)
            v = GetModuleFileNameW(NULL, b, size);
    } while (b && v == size);

    if (b)
        *(b + size) = 0;
    std::wstring res(b);
    std::free(b);

    executable = std::filesystem::path(std::string(res.begin(), res.end()));
    return executable.remove_filename().string();
#else
#error Not implemented
#endif
}

PosixFile::PosixFile(int fileHandle)
{
    m_fileHandle = fileHandle;
}

PosixFile::~PosixFile()
{
    if (m_fileHandle >= 0)
        close();
}

uint64_t PosixFile::size()
{
    struct stat64 stat_buf;
    int rc = ::fstat64(m_fileHandle, &stat_buf);
    return rc == 0 ? stat_buf.st_size : 0;
}

int PosixFile::read(unsigned char *outputBuffer, uint64_t startOffset, unsigned int readSize)
{
    off_t offt = ::lseek(m_fileHandle, startOffset, SEEK_SET);
    if (offt == -1) {
        Qul::PlatformInterface::log("Failed to seek: %s\n", strerror(errno));
        return -1;
    }

    ssize_t actualSize = ::read(m_fileHandle, outputBuffer, readSize);
    if (actualSize != readSize) {
        Qul::PlatformInterface::log("Unable to read enough data: %s\n", strerror(errno));
    }
    return actualSize;
}

int PosixFile::close()
{
    if (::close(m_fileHandle) != 0) {
        Qul::PlatformInterface::log("Unable to close file: %s\n", strerror(errno));
        return -1;
    }
    m_fileHandle = -1;
    return 0;
}

Qul::PlatformInterface::File *PosixFilesystem::open(const char *fileName, Qul::PlatformInterface::File::Mode mode)
{
    if (strlen(fileName) == 0) {
        Qul::PlatformInterface::log("File name is empty.\n");
        return nullptr;
    }

    std::filesystem::path path(fileName);

    if (path.is_relative()) {
        // In case the path is relative, it is treated relative to the application binary.
        path = std::filesystem::path(appFilePath()) / path;
    }
#ifndef NDEBUG
    Qul::PlatformInterface::log("Loading file %s\n", path.string().c_str());
#endif

#ifdef QUL_OS_LINUX
    int flags = O_CLOEXEC;
#elif defined(QUL_OS_MACOS)
    int flags = O_CLOEXEC;
#elif defined(QUL_OS_WIN)
    int flags = O_BINARY;
#endif

    if (mode == Qul::PlatformInterface::File::Mode::ReadOnly)
        flags |= O_RDONLY;
    else {
        Qul::PlatformInterface::log("Only Read-Only is supported");
        return nullptr;
    }

    if (std::filesystem::is_directory(path)) {
        Qul::PlatformInterface::log("Could not open file '%s' because it is a directory.\n",
                                    path.make_preferred().string().c_str());
        return nullptr;
    }

    int handle = ::open(path.make_preferred().string().c_str(), flags);

    if (handle < 0) {
        Qul::PlatformInterface::log("Could not open file '%s': %s\n",
                                    path.make_preferred().string().c_str(),
                                    strerror(errno));
        return nullptr;
    }

    return Qul::PlatformInterface::qul_new<PosixFile>(handle);
}