// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only

#ifndef QFILESYSTEMMETADATA_P_H
#define QFILESYSTEMMETADATA_P_H

//
//  W A R N I N G
//  -------------
//
// This file is not part of the Qt API.  It exists purely as an
// implementation detail.  This header file may change from version to
// version without notice, or even be removed.
//
// We mean it.
//

#include "qplatformdefs.h"
#include <QtCore/qglobal.h>
#include <QtCore/qdatetime.h>
#include <QtCore/qtimezone.h>
#include <QtCore/private/qabstractfileengine_p.h>

// Platform-specific includes
#ifdef Q_OS_WIN
#  include <QtCore/qt_windows.h>
#  ifndef IO_REPARSE_TAG_SYMLINK
#     define IO_REPARSE_TAG_SYMLINK (0xA000000CL)
#  endif
#endif

#ifdef Q_OS_UNIX
struct statx;
#endif

QT_BEGIN_NAMESPACE

class QFileSystemEngine;

class Q_AUTOTEST_EXPORT QFileSystemMetaData
{
public:
    QFileSystemMetaData()
        : size_(-1)
    {
    }

    enum MetaDataFlag {
        // Permissions, overlaps with QFile::Permissions
        OtherReadPermission = 0x00000004,   OtherWritePermission = 0x00000002,  OtherExecutePermission = 0x00000001,
        GroupReadPermission = 0x00000040,   GroupWritePermission = 0x00000020,  GroupExecutePermission = 0x00000010,
        UserReadPermission  = 0x00000400,   UserWritePermission  = 0x00000200,  UserExecutePermission  = 0x00000100,
        OwnerReadPermission = 0x00004000,   OwnerWritePermission = 0x00002000,  OwnerExecutePermission = 0x00001000,

        OtherPermissions    = OtherReadPermission | OtherWritePermission | OtherExecutePermission,
        GroupPermissions    = GroupReadPermission | GroupWritePermission | GroupExecutePermission,
        UserPermissions     = UserReadPermission  | UserWritePermission  | UserExecutePermission,
        OwnerPermissions    = OwnerReadPermission | OwnerWritePermission | OwnerExecutePermission,

        ReadPermissions     = OtherReadPermission | GroupReadPermission | UserReadPermission | OwnerReadPermission,
        WritePermissions    = OtherWritePermission | GroupWritePermission | UserWritePermission | OwnerWritePermission,
        ExecutePermissions  = OtherExecutePermission | GroupExecutePermission | UserExecutePermission | OwnerExecutePermission,

        Permissions         = OtherPermissions | GroupPermissions | UserPermissions | OwnerPermissions,

        // Type
        LinkType            = 0x00010000,
        FileType            = 0x00020000,
        DirectoryType       = 0x00040000,
#if defined(Q_OS_DARWIN)
        BundleType          = 0x00080000,
        AliasType           = 0x08000000,
#else
        BundleType          =        0x0,
        AliasType           =        0x0,
#endif
#if defined(Q_OS_WIN)
        JunctionType        = 0x04000000,
        WinLnkType          = 0x08000000,   // Note: Uses the same position for AliasType on Mac
#else
        JunctionType        =        0x0,
        WinLnkType          =        0x0,
#endif
        SequentialType      = 0x00800000,   // Note: overlaps with QAbstractFileEngine::RootFlag

        LegacyLinkType      = LinkType | AliasType | WinLnkType,

        Type                = LinkType | FileType | DirectoryType | BundleType | SequentialType | AliasType,

        // Attributes
        HiddenAttribute     = 0x00100000,
        SizeAttribute       = 0x00200000,   // Note: overlaps with QAbstractFileEngine::LocalDiskFlag
        ExistsAttribute     = 0x00400000,   // For historical reasons, indicates existence of data, not the file
#if defined(Q_OS_WIN)
        WasDeletedAttribute =        0x0,
#else
        WasDeletedAttribute = 0x40000000,   // Indicates the file was deleted
#endif

        Attributes          = HiddenAttribute | SizeAttribute | ExistsAttribute | WasDeletedAttribute,

        // Times - if we know one of them, we know them all
        AccessTime          = 0x02000000,
        BirthTime           = 0x02000000,
        MetadataChangeTime  = 0x02000000,
        ModificationTime    = 0x02000000,

        Times               = AccessTime | BirthTime | MetadataChangeTime | ModificationTime,

        // Owner IDs
        UserId              = 0x10000000,
        GroupId             = 0x20000000,

        OwnerIds            = UserId | GroupId,

        PosixStatFlags      = QFileSystemMetaData::OtherPermissions
                            | QFileSystemMetaData::GroupPermissions
                            | QFileSystemMetaData::OwnerPermissions
                            | QFileSystemMetaData::FileType
                            | QFileSystemMetaData::DirectoryType
                            | QFileSystemMetaData::SequentialType
                            | QFileSystemMetaData::SizeAttribute
                            | QFileSystemMetaData::WasDeletedAttribute
                            | QFileSystemMetaData::Times
                            | QFileSystemMetaData::OwnerIds,

#if defined(Q_OS_WIN)
        WinStatFlags        = QFileSystemMetaData::FileType
                            | QFileSystemMetaData::DirectoryType
                            | QFileSystemMetaData::HiddenAttribute
                            | QFileSystemMetaData::ExistsAttribute
                            | QFileSystemMetaData::SizeAttribute
                            | QFileSystemMetaData::Times,
#endif

        AllMetaDataFlags    = 0xFFFFFFFF

    };
    Q_DECLARE_FLAGS(MetaDataFlags, MetaDataFlag)

    bool hasFlags(MetaDataFlags flags) const
    {
        return ((knownFlagsMask & flags) == flags);
    }

    MetaDataFlags missingFlags(MetaDataFlags flags)
    {
        return flags & ~knownFlagsMask;
    }

    void clear()
    {
        knownFlagsMask = {};
    }

    void clearFlags(MetaDataFlags flags = AllMetaDataFlags)
    {
        knownFlagsMask &= ~flags;
    }

    bool exists() const                     { return entryFlags.testAnyFlag(ExistsAttribute); }

    bool isLink() const                     { return entryFlags.testAnyFlag(LinkType); }
    bool isFile() const                     { return entryFlags.testAnyFlag(FileType); }
    bool isDirectory() const                { return entryFlags.testAnyFlag(DirectoryType); }
    bool isBundle() const;
    bool isAlias() const;
    bool isLegacyLink() const               { return entryFlags.testAnyFlag(LegacyLinkType); }
    bool isSequential() const               { return entryFlags.testAnyFlag(SequentialType); }
    bool isHidden() const                   { return entryFlags.testAnyFlag(HiddenAttribute); }
    bool wasDeleted() const                 { return entryFlags.testAnyFlag(WasDeletedAttribute); }
#if defined(Q_OS_WIN)
    bool isLnkFile() const                  { return entryFlags.testAnyFlag(WinLnkType); }
    bool isJunction() const                 { return entryFlags.testAnyFlag(JunctionType); }
#else
    bool isLnkFile() const                  { return false; }
    bool isJunction() const                 { return false; }
#endif

    qint64 size() const                     { return size_; }

    QFile::Permissions permissions() const;

    QDateTime accessTime() const;
    QDateTime birthTime() const;
    QDateTime metadataChangeTime() const;
    QDateTime modificationTime() const;

    QDateTime fileTime(QFile::FileTime time) const;
    uint userId() const;
    uint groupId() const;
    uint ownerId(QAbstractFileEngine::FileOwner owner) const;

#ifdef Q_OS_UNIX
    void fillFromStatxBuf(const struct statx &statBuffer);
    void fillFromStatBuf(const QT_STATBUF &statBuffer);
    void fillFromDirEnt(const QT_DIRENT &statBuffer);
#endif

#if defined(Q_OS_WIN)
    inline void fillFromFileAttribute(DWORD fileAttribute, bool isDriveRoot = false);
    inline void fillFromFindData(WIN32_FIND_DATA &findData, bool setLinkType = false, bool isDriveRoot = false);
    inline void fillFromFindInfo(BY_HANDLE_FILE_INFORMATION &fileInfo);
#endif
private:
    friend class QFileSystemEngine;

    MetaDataFlags knownFlagsMask;
    MetaDataFlags entryFlags;

    qint64 size_ = 0;

    // Platform-specific data goes here:
#if defined(Q_OS_WIN)
    DWORD fileAttribute_;
    FILETIME birthTime_;
    FILETIME changeTime_;
    FILETIME lastAccessTime_;
    FILETIME lastWriteTime_;
#else
    // msec precision
    qint64 accessTime_ = 0;
    qint64 birthTime_ = 0;
    qint64 metadataChangeTime_ = 0;
    qint64 modificationTime_ = 0;

    uint userId_ = (uint) -2;
    uint groupId_ = (uint) -2;
#endif

};

Q_DECLARE_OPERATORS_FOR_FLAGS(QFileSystemMetaData::MetaDataFlags)

inline QFile::Permissions QFileSystemMetaData::permissions() const { return QFile::Permissions::fromInt((Permissions & entryFlags).toInt()); }

#if defined(Q_OS_DARWIN)
inline bool QFileSystemMetaData::isBundle() const                   { return entryFlags.testAnyFlag(BundleType); }
inline bool QFileSystemMetaData::isAlias() const                    { return entryFlags.testAnyFlag(AliasType); }
#else
inline bool QFileSystemMetaData::isBundle() const                   { return false; }
inline bool QFileSystemMetaData::isAlias() const                    { return false; }
#endif

#if defined(Q_OS_UNIX) || defined (Q_OS_WIN)
inline QDateTime QFileSystemMetaData::fileTime(QFile::FileTime time) const
{
    switch (time) {
    case QFile::FileModificationTime:
        return modificationTime();

    case QFile::FileAccessTime:
        return accessTime();

    case QFile::FileBirthTime:
        return birthTime();

    case QFile::FileMetadataChangeTime:
        return metadataChangeTime();
    }

    return QDateTime();
}
#endif

#if defined(Q_OS_UNIX)
inline QDateTime QFileSystemMetaData::birthTime() const
{
    return birthTime_
        ? QDateTime::fromMSecsSinceEpoch(birthTime_, QTimeZone::UTC)
        : QDateTime();
}
inline QDateTime QFileSystemMetaData::metadataChangeTime() const
{
    return metadataChangeTime_
        ? QDateTime::fromMSecsSinceEpoch(metadataChangeTime_, QTimeZone::UTC)
        : QDateTime();
}
inline QDateTime QFileSystemMetaData::modificationTime() const
{
    return modificationTime_
        ? QDateTime::fromMSecsSinceEpoch(modificationTime_, QTimeZone::UTC)
        : QDateTime();
}
inline QDateTime QFileSystemMetaData::accessTime() const
{
    return accessTime_
        ? QDateTime::fromMSecsSinceEpoch(accessTime_, QTimeZone::UTC)
        : QDateTime();
}

inline uint QFileSystemMetaData::userId() const                     { return userId_; }
inline uint QFileSystemMetaData::groupId() const                    { return groupId_; }

inline uint QFileSystemMetaData::ownerId(QAbstractFileEngine::FileOwner owner) const
{
    if (owner == QAbstractFileEngine::OwnerUser)
        return userId();
    else
        return groupId();
}
#endif

#if defined(Q_OS_WIN)
inline uint QFileSystemMetaData::userId() const                     { return (uint) -2; }
inline uint QFileSystemMetaData::groupId() const                    { return (uint) -2; }
inline uint QFileSystemMetaData::ownerId(QAbstractFileEngine::FileOwner owner) const
{
    if (owner == QAbstractFileEngine::OwnerUser)
        return userId();
    else
        return groupId();
}

inline void QFileSystemMetaData::fillFromFileAttribute(DWORD fileAttribute,bool isDriveRoot)
{
    fileAttribute_ = fileAttribute;
    // Ignore the hidden attribute for drives.
    if (!isDriveRoot && (fileAttribute_ & FILE_ATTRIBUTE_HIDDEN))
        entryFlags |= HiddenAttribute;
    entryFlags |= ((fileAttribute & FILE_ATTRIBUTE_DIRECTORY) ? DirectoryType: FileType);
    entryFlags |= ExistsAttribute;
    knownFlagsMask |= FileType | DirectoryType | HiddenAttribute | ExistsAttribute;
}

inline void QFileSystemMetaData::fillFromFindData(WIN32_FIND_DATA &findData, bool setLinkType, bool isDriveRoot)
{
    fillFromFileAttribute(findData.dwFileAttributes, isDriveRoot);
    birthTime_ = findData.ftCreationTime;
    lastAccessTime_ = findData.ftLastAccessTime;
    changeTime_ = lastWriteTime_ = findData.ftLastWriteTime;
    if (fileAttribute_ & FILE_ATTRIBUTE_DIRECTORY) {
        size_ = 0;
    } else {
        size_ = findData.nFileSizeHigh;
        size_ <<= 32;
        size_ += findData.nFileSizeLow;
    }
    knownFlagsMask |=  Times | SizeAttribute;
    if (setLinkType) {
        knownFlagsMask |=  LinkType;
        entryFlags &= ~LinkType;
        if (fileAttribute_ & FILE_ATTRIBUTE_REPARSE_POINT) {
            if (findData.dwReserved0 == IO_REPARSE_TAG_SYMLINK) {
                entryFlags |= LinkType;
#if defined(IO_REPARSE_TAG_MOUNT_POINT)
            } else if ((fileAttribute_ & FILE_ATTRIBUTE_DIRECTORY)
                    && (findData.dwReserved0 == IO_REPARSE_TAG_MOUNT_POINT)) {
                entryFlags |= JunctionType;
#endif
            }
        }
    }
}

inline void QFileSystemMetaData::fillFromFindInfo(BY_HANDLE_FILE_INFORMATION &fileInfo)
{
    fillFromFileAttribute(fileInfo.dwFileAttributes);
    birthTime_ = fileInfo.ftCreationTime;
    lastAccessTime_ = fileInfo.ftLastAccessTime;
    changeTime_ = lastWriteTime_ = fileInfo.ftLastWriteTime;
    if (fileAttribute_ & FILE_ATTRIBUTE_DIRECTORY) {
        size_ = 0;
    } else {
        size_ = fileInfo.nFileSizeHigh;
        size_ <<= 32;
        size_ += fileInfo.nFileSizeLow;
    }
    knownFlagsMask |=  Times | SizeAttribute;
}
#endif // Q_OS_WIN

QT_END_NAMESPACE

#endif // include guard
