FreeCAD / src /Mod /Start /App /DisplayedFilesModel.cpp
AbdulElahGwaith's picture
Upload folder using huggingface_hub
985c397 verified
// SPDX-License-Identifier: LGPL-2.1-or-later
/****************************************************************************
* *
* Copyright (c) 2024 The FreeCAD Project Association AISBL *
* *
* This file is part of FreeCAD. *
* *
* FreeCAD is free software: you can redistribute it and/or modify it *
* under the terms of the GNU Lesser General Public License as *
* published by the Free Software Foundation, either version 2.1 of the *
* License, or (at your option) any later version. *
* *
* FreeCAD 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 *
* Lesser General Public License for more details. *
* *
* You should have received a copy of the GNU Lesser General Public *
* License along with FreeCAD. If not, see *
* <https://www.gnu.org/licenses/>. *
* *
***************************************************************************/
#include <boost/algorithm/string/predicate.hpp>
#include <QByteArray>
#include <QFileInfo>
#include <QProcess>
#include <QTimeZone>
#include <QThreadPool>
#include <QUrl>
#include "DisplayedFilesModel.h"
#include "FileUtilities.h"
#include "ThumbnailSource.h"
#include <App/Application.h>
#include <App/ProjectFile.h>
#include <Base/FileInfo.h>
#include <Base/TimeInfo.h>
#include <Base/Stream.h>
using namespace Start;
FileStats fileInfoFromFreeCADFile(const std::string& path)
{
App::ProjectFile proj(path);
proj.loadDocument();
auto metadata = proj.getMetadata();
FileStats result;
result.insert(std::make_pair(DisplayedFilesModelRoles::author, metadata.createdBy));
result.insert(std::make_pair(DisplayedFilesModelRoles::modifiedTime, metadata.lastModifiedDate));
result.insert(std::make_pair(DisplayedFilesModelRoles::creationTime, metadata.creationDate));
result.insert(std::make_pair(DisplayedFilesModelRoles::company, metadata.company));
result.insert(std::make_pair(DisplayedFilesModelRoles::license, metadata.license));
result.insert(std::make_pair(DisplayedFilesModelRoles::description, metadata.comment));
return result;
}
/// Load the thumbnail image data (if any) that is stored in an FCStd file.
/// \returns The image bytes, or an empty QByteArray (if no thumbnail was stored)
QByteArray loadFCStdThumbnail(const QString& pathToFCStdFile)
{
if (App::ProjectFile proj(pathToFCStdFile.toStdString()); proj.loadDocument()) {
try {
const QString pathToCachedThumbnail = getPathToCachedThumbnail(pathToFCStdFile);
if (!useCachedThumbnail(pathToCachedThumbnail, pathToFCStdFile)) {
static const QString pathToThumbnail = defaultThumbnailPath;
if (proj.containsFile(pathToThumbnail.toStdString())) {
createThumbnailsDir();
const Base::FileInfo fi(pathToCachedThumbnail.toStdString());
Base::ofstream stream(fi, std::ios::out | std::ios::binary);
proj.readInputFileDirect(pathToThumbnail.toStdString(), stream);
stream.close();
}
}
if (auto inputFile = QFile(pathToCachedThumbnail); inputFile.exists()) {
inputFile.open(QIODevice::OpenModeFlag::ReadOnly);
return inputFile.readAll();
}
}
catch (...) {
Base::Console().log("Failed to load thumbnail for %s\n", pathToFCStdFile.toStdString());
}
}
return {};
}
FileStats getFileInfo(const std::string& path)
{
FileStats result;
const Base::FileInfo file(path);
if (file.hasExtension("FCStd")) {
result = fileInfoFromFreeCADFile(path);
}
else {
result.insert(
std::make_pair(DisplayedFilesModelRoles::modifiedTime, getLastModifiedAsString(file))
);
}
result.insert(std::make_pair(DisplayedFilesModelRoles::path, path));
result.insert(std::make_pair(DisplayedFilesModelRoles::size, humanReadableSize(file.size())));
result.insert(std::make_pair(DisplayedFilesModelRoles::baseName, file.fileName()));
return result;
}
bool freecadCanOpen(const QString& extension)
{
std::string ext = extension.toStdString();
auto importTypes = App::GetApplication().getImportTypes();
return std::ranges::find_if(
importTypes,
[&ext](const auto& item) { return boost::iequals(item, ext); }
)
!= importTypes.end();
}
DisplayedFilesModel::DisplayedFilesModel(QObject* parent)
: QAbstractListModel(parent)
{}
int DisplayedFilesModel::rowCount(const QModelIndex& parent) const
{
Q_UNUSED(parent);
return static_cast<int>(_fileInfoCache.size());
}
QVariant DisplayedFilesModel::data(const QModelIndex& index, int role) const
{
const int row = index.row();
if (row < 0 || row >= static_cast<int>(_fileInfoCache.size())) {
return {};
}
const auto mapEntry = _fileInfoCache.at(row);
switch (const auto roleAsType = static_cast<DisplayedFilesModelRoles>(role)) {
case DisplayedFilesModelRoles::author: // NOLINT(bugprone-branch-clone)
[[fallthrough]];
case DisplayedFilesModelRoles::baseName:
[[fallthrough]];
case DisplayedFilesModelRoles::company:
[[fallthrough]];
case DisplayedFilesModelRoles::creationTime:
[[fallthrough]];
case DisplayedFilesModelRoles::description:
[[fallthrough]];
case DisplayedFilesModelRoles::license:
[[fallthrough]];
case DisplayedFilesModelRoles::modifiedTime:
[[fallthrough]];
case DisplayedFilesModelRoles::path:
[[fallthrough]];
case DisplayedFilesModelRoles::size:
if (mapEntry.contains(roleAsType)) {
return QString::fromStdString(mapEntry.at(roleAsType));
}
break;
case DisplayedFilesModelRoles::image: {
if (const auto path = QString::fromStdString(mapEntry.at(DisplayedFilesModelRoles::path));
_imageCache.contains(path)) {
return _imageCache[path];
}
break;
}
default:
break;
}
switch (role) {
case Qt::ItemDataRole::ToolTipRole:
return QString::fromStdString(mapEntry.at(DisplayedFilesModelRoles::path));
default:
// No other role gets handled
break;
}
return {};
}
void DisplayedFilesModel::addFile(const QString& filePath)
{
const QFileInfo qfi(filePath);
if (!qfi.isReadable()) {
return;
}
if (!freecadCanOpen(qfi.suffix())) {
return;
}
_fileInfoCache.emplace_back(getFileInfo(filePath.toStdString()));
const auto lowercaseExtension = qfi.suffix().toLower();
const QStringList ignoredExtensions {
QLatin1String("fcmacro"),
QLatin1String("py"),
QLatin1String("pyi"),
QLatin1String("csv"),
QLatin1String("txt")
};
if (lowercaseExtension == QLatin1String("fcstd")) {
if (const auto thumbnail = loadFCStdThumbnail(filePath); !thumbnail.isEmpty()) {
_imageCache.insert(filePath, thumbnail);
}
}
else if (ignoredExtensions.contains(lowercaseExtension)) {
// Don't try to generate a thumbnail for things like this: FreeCAD can read them, but
// there's not much point in showing anything besides a generic icon
}
else {
const auto runner = new ThumbnailSource(filePath);
connect(
runner->signals(),
&ThumbnailSourceSignals::thumbnailAvailable,
this,
&DisplayedFilesModel::processNewThumbnail
);
QThreadPool::globalInstance()->start(runner);
}
}
void DisplayedFilesModel::clear()
{
_fileInfoCache.clear();
}
QHash<int, QByteArray> DisplayedFilesModel::roleNames() const
{
static QHash<int, QByteArray> nameMap {
std::make_pair(static_cast<int>(DisplayedFilesModelRoles::author), "author"),
std::make_pair(static_cast<int>(DisplayedFilesModelRoles::baseName), "baseName"),
std::make_pair(static_cast<int>(DisplayedFilesModelRoles::company), "company"),
std::make_pair(static_cast<int>(DisplayedFilesModelRoles::creationTime), "creationTime"),
std::make_pair(static_cast<int>(DisplayedFilesModelRoles::description), "description"),
std::make_pair(static_cast<int>(DisplayedFilesModelRoles::image), "image"),
std::make_pair(static_cast<int>(DisplayedFilesModelRoles::license), "license"),
std::make_pair(static_cast<int>(DisplayedFilesModelRoles::modifiedTime), "modifiedTime"),
std::make_pair(static_cast<int>(DisplayedFilesModelRoles::path), "path"),
std::make_pair(static_cast<int>(DisplayedFilesModelRoles::size), "size"),
};
return nameMap;
}
void DisplayedFilesModel::processNewThumbnail(const QString& file, const QByteArray& thumbnail)
{
if (!thumbnail.isEmpty()) {
_imageCache.insert(file, thumbnail);
// Figure out the index of this file...
auto it = std::ranges::find_if(_fileInfoCache, [file](const FileStats& row) {
auto pathIt = row.find(DisplayedFilesModelRoles::path);
return pathIt != row.end() && pathIt->second == file.toStdString();
});
if (it != _fileInfoCache.end()) {
std::size_t index = std::distance(_fileInfoCache.begin(), it);
QModelIndex qmi = createIndex(index, 0);
Q_EMIT(dataChanged(qmi, qmi, {static_cast<int>(DisplayedFilesModelRoles::image)}));
}
else {
Base::Console().log("Unrecognized path %s\n", file.toStdString());
}
}
}