This adds isNullOrEmpty string helper that cheks if string is... well null or empty. It is done to improve readability of the code and better express intent.
634 lines
22 KiB
C++
634 lines
22 KiB
C++
/***************************************************************************
|
|
* Copyright (c) 2008 Jürgen Riegel <juergen.riegel@web.de> *
|
|
* *
|
|
* This file is part of the FreeCAD CAx development system. *
|
|
* *
|
|
* This library is free software; you can redistribute it and/or *
|
|
* modify it under the terms of the GNU Library General Public *
|
|
* License as published by the Free Software Foundation; either *
|
|
* version 2 of the License, or (at your option) any later version. *
|
|
* *
|
|
* This library 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 Library General Public License for more details. *
|
|
* *
|
|
* You should have received a copy of the GNU Library General Public *
|
|
* License along with this library; see the file COPYING.LIB. If not, *
|
|
* write to the Free Software Foundation, Inc., 59 Temple Place, *
|
|
* Suite 330, Boston, MA 02111-1307, USA *
|
|
* *
|
|
***************************************************************************/
|
|
|
|
|
|
#include "PreCompiled.h"
|
|
|
|
#include <Base/Console.h>
|
|
#include <Base/PyObjectBase.h>
|
|
#include <Base/Reader.h>
|
|
#include <Base/Stream.h>
|
|
#include <Base/Writer.h>
|
|
#include <Base/Uuid.h>
|
|
#include <Base/Tools.h>
|
|
|
|
#include "PropertyFile.h"
|
|
#include "Document.h"
|
|
#include "DocumentObject.h"
|
|
#include "PropertyContainer.h"
|
|
|
|
|
|
using namespace App;
|
|
using namespace Base;
|
|
using namespace std;
|
|
|
|
|
|
//**************************************************************************
|
|
// PropertyFileIncluded
|
|
//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
|
|
|
TYPESYSTEM_SOURCE(App::PropertyFileIncluded, App::Property)
|
|
|
|
|
|
PropertyFileIncluded::PropertyFileIncluded() = default;
|
|
|
|
PropertyFileIncluded::~PropertyFileIncluded()
|
|
{
|
|
// clean up
|
|
if (!_cValue.empty()) {
|
|
Base::FileInfo file(_cValue.c_str());
|
|
file.setPermissions(Base::FileInfo::ReadWrite);
|
|
file.deleteFile();
|
|
}
|
|
}
|
|
|
|
void PropertyFileIncluded::aboutToSetValue()
|
|
{
|
|
// This is a trick to check in Copy() if it is called
|
|
// directly from outside or by the Undo/Redo mechanism.
|
|
// In the latter case it is sufficient to rename the file
|
|
// because another file will be assigned afterwards.
|
|
// If Copy() is directly called (e.g. to copy the file to
|
|
// another document) a copy of the file needs to be created.
|
|
// This copy will be deleted again in the class destructor.
|
|
this->StatusBits.set(10);
|
|
Property::aboutToSetValue();
|
|
this->StatusBits.reset(10);
|
|
}
|
|
|
|
std::string PropertyFileIncluded::getDocTransientPath() const
|
|
{
|
|
std::string path;
|
|
PropertyContainer* co = getContainer();
|
|
if (co->isDerivedFrom<DocumentObject>()) {
|
|
path = static_cast<DocumentObject*>(co)->getDocument()->TransientDir.getValue();
|
|
std::replace(path.begin(), path.end(), '\\', '/');
|
|
}
|
|
return path;
|
|
}
|
|
|
|
std::string PropertyFileIncluded::getUniqueFileName(const std::string& path,
|
|
const std::string& filename) const
|
|
{
|
|
Base::Uuid uuid;
|
|
Base::FileInfo fi(path + "/" + filename);
|
|
while (fi.exists()) {
|
|
fi.setFile(path + "/" + filename + "." + uuid.getValue());
|
|
}
|
|
|
|
return fi.filePath();
|
|
}
|
|
|
|
std::string PropertyFileIncluded::getExchangeTempFile() const
|
|
{
|
|
return Base::FileInfo::getTempFileName(Base::FileInfo(getValue()).fileName().c_str(),
|
|
getDocTransientPath().c_str());
|
|
}
|
|
|
|
std::string PropertyFileIncluded::getOriginalFileName() const
|
|
{
|
|
return _OriginalName;
|
|
}
|
|
|
|
void PropertyFileIncluded::setValue(const char* sFile, const char* sName)
|
|
{
|
|
if (!Base::Tools::isNullOrEmpty(sFile)) {
|
|
if (_cValue == sFile) {
|
|
throw Base::FileSystemError("Not possible to set the same file!");
|
|
}
|
|
|
|
// keep the path to the original file
|
|
_OriginalName = sFile;
|
|
|
|
std::string pathTrans = getDocTransientPath();
|
|
Base::FileInfo file(sFile);
|
|
std::string path = file.dirPath();
|
|
if (!file.exists()) {
|
|
std::stringstream str;
|
|
str << "File " << file.filePath() << " does not exist.";
|
|
throw Base::FileSystemError(str.str());
|
|
}
|
|
|
|
aboutToSetValue(); // undo/redo by moving the file away with temp name
|
|
|
|
// remove old file (if not moved by undo)
|
|
Base::FileInfo value(_cValue);
|
|
std::string pathAct = value.dirPath();
|
|
if (value.exists()) {
|
|
value.setPermissions(Base::FileInfo::ReadWrite);
|
|
value.deleteFile();
|
|
}
|
|
|
|
// if a special name given, use this instead
|
|
if (sName) {
|
|
Base::FileInfo fi(pathTrans + "/" + sName);
|
|
if (fi.exists()) {
|
|
// if a file with this name already exists search for a new one
|
|
std::string dir = pathTrans;
|
|
std::string fnp = fi.fileNamePure();
|
|
std::string ext = fi.extension();
|
|
int i = 0;
|
|
do {
|
|
i++;
|
|
std::stringstream str;
|
|
str << dir << "/" << fnp << i;
|
|
if (!ext.empty()) {
|
|
str << "." << ext;
|
|
}
|
|
fi.setFile(str.str());
|
|
} while (fi.exists());
|
|
|
|
_cValue = fi.filePath();
|
|
_BaseFileName = fi.fileName();
|
|
}
|
|
else {
|
|
_cValue = pathTrans + "/" + sName;
|
|
_BaseFileName = sName;
|
|
}
|
|
}
|
|
else if (value.fileName().empty()) {
|
|
_cValue = pathTrans + "/" + file.fileName();
|
|
_BaseFileName = file.fileName();
|
|
}
|
|
|
|
// The following applies only on files that are inside the transient
|
|
// directory:
|
|
// When a file is read-only it is supposed to be assigned to a
|
|
// PropertyFileIncluded instance. In this case we must copy the
|
|
// file because otherwise the above instance loses its data.
|
|
// If the file is writable it is supposed to be of free use and
|
|
// it can be simply renamed.
|
|
|
|
// if the file is already in transient dir of the document, just use it
|
|
if (path == pathTrans && file.isWritable()) {
|
|
bool done = file.renameFile(_cValue.c_str());
|
|
if (!done) {
|
|
std::stringstream str;
|
|
str << "Cannot rename file " << file.filePath() << " to " << _cValue;
|
|
throw Base::FileSystemError(str.str());
|
|
}
|
|
|
|
// make the file read-only
|
|
Base::FileInfo dst(_cValue);
|
|
dst.setPermissions(Base::FileInfo::ReadOnly);
|
|
}
|
|
// otherwise copy from origin location
|
|
else {
|
|
// if file already exists in transient dir make a new unique name
|
|
Base::FileInfo fi(_cValue);
|
|
if (fi.exists()) {
|
|
// if a file with this name already exists search for a new one
|
|
std::string dir = fi.dirPath();
|
|
std::string fnp = fi.fileNamePure();
|
|
std::string ext = fi.extension();
|
|
int i = 0;
|
|
do {
|
|
i++;
|
|
std::stringstream str;
|
|
str << dir << "/" << fnp << i;
|
|
if (!ext.empty()) {
|
|
str << "." << ext;
|
|
}
|
|
fi.setFile(str.str());
|
|
} while (fi.exists());
|
|
|
|
_cValue = fi.filePath();
|
|
_BaseFileName = fi.fileName();
|
|
}
|
|
|
|
bool done = file.copyTo(_cValue.c_str());
|
|
if (!done) {
|
|
std::stringstream str;
|
|
str << "Cannot copy file from " << file.filePath() << " to " << _cValue;
|
|
throw Base::FileSystemError(str.str());
|
|
}
|
|
|
|
// make the file read-only
|
|
Base::FileInfo dst(_cValue);
|
|
dst.setPermissions(Base::FileInfo::ReadOnly);
|
|
}
|
|
|
|
hasSetValue();
|
|
}
|
|
}
|
|
|
|
const char* PropertyFileIncluded::getValue() const
|
|
{
|
|
return _cValue.c_str();
|
|
}
|
|
|
|
PyObject* PropertyFileIncluded::getPyObject()
|
|
{
|
|
PyObject* p = PyUnicode_DecodeUTF8(_cValue.c_str(), _cValue.size(), nullptr);
|
|
if (!p) {
|
|
throw Base::UnicodeError("PropertyFileIncluded: UTF-8 conversion failure");
|
|
}
|
|
return p;
|
|
}
|
|
|
|
namespace App
|
|
{
|
|
const char* getNameFromFile(PyObject* value)
|
|
{
|
|
const char* string = nullptr;
|
|
PyObject* oname = PyObject_GetAttrString(value, "name");
|
|
if (oname) {
|
|
if (PyUnicode_Check(oname)) {
|
|
string = PyUnicode_AsUTF8(oname);
|
|
}
|
|
else if (PyBytes_Check(oname)) {
|
|
string = PyBytes_AsString(oname);
|
|
}
|
|
Py_DECREF(oname);
|
|
}
|
|
|
|
if (!string) {
|
|
throw Base::TypeError("Unable to get filename");
|
|
}
|
|
return string;
|
|
}
|
|
|
|
|
|
bool isIOFile(PyObject* file)
|
|
{
|
|
PyObject* io = PyImport_ImportModule("io");
|
|
PyObject* IOBase_Class = PyObject_GetAttrString(io, "IOBase");
|
|
bool isFile = PyObject_IsInstance(file, IOBase_Class);
|
|
Py_DECREF(IOBase_Class);
|
|
Py_DECREF(io);
|
|
return isFile;
|
|
}
|
|
} // namespace App
|
|
|
|
void PropertyFileIncluded::setPyObject(PyObject* value)
|
|
{
|
|
if (PyUnicode_Check(value)) {
|
|
std::string string = PyUnicode_AsUTF8(value);
|
|
setValue(string.c_str());
|
|
}
|
|
else if (PyBytes_Check(value)) {
|
|
std::string string = PyBytes_AsString(value);
|
|
setValue(string.c_str());
|
|
}
|
|
else if (isIOFile(value)) {
|
|
std::string string = getNameFromFile(value);
|
|
setValue(string.c_str());
|
|
}
|
|
else if (PyTuple_Check(value)) {
|
|
if (PyTuple_Size(value) != 2) {
|
|
throw Base::TypeError("Tuple needs size of (filePath,newFileName)");
|
|
}
|
|
PyObject* file = PyTuple_GetItem(value, 0);
|
|
PyObject* name = PyTuple_GetItem(value, 1);
|
|
|
|
// decoding file
|
|
std::string fileStr;
|
|
if (PyUnicode_Check(file)) {
|
|
fileStr = PyUnicode_AsUTF8(file);
|
|
}
|
|
else if (PyBytes_Check(file)) {
|
|
fileStr = PyBytes_AsString(file);
|
|
}
|
|
else if (isIOFile(value)) {
|
|
fileStr = getNameFromFile(file);
|
|
}
|
|
else {
|
|
std::string error = std::string("First item in tuple must be a file or string, not ");
|
|
error += file->ob_type->tp_name;
|
|
throw Base::TypeError(error);
|
|
}
|
|
|
|
// decoding name
|
|
std::string nameStr;
|
|
if (PyUnicode_Check(name)) {
|
|
nameStr = PyUnicode_AsUTF8(name);
|
|
}
|
|
else if (PyBytes_Check(name)) {
|
|
nameStr = PyBytes_AsString(name);
|
|
}
|
|
else if (isIOFile(value)) {
|
|
nameStr = getNameFromFile(name);
|
|
}
|
|
else {
|
|
std::string error = std::string("Second item in tuple must be a string, not ");
|
|
error += name->ob_type->tp_name;
|
|
throw Base::TypeError(error);
|
|
}
|
|
|
|
setValue(fileStr.c_str(), nameStr.c_str());
|
|
}
|
|
else if (PyDict_Check(value)) {
|
|
Py::Dict dict(value);
|
|
if (dict.hasKey("filter")) {
|
|
setFilter(Py::String(dict.getItem("filter")));
|
|
}
|
|
if (dict.hasKey("filename")) {
|
|
std::string string = static_cast<std::string>(Py::String(dict.getItem("filename")));
|
|
setValue(string.c_str());
|
|
}
|
|
}
|
|
else {
|
|
std::string error = std::string("Type must be string or file, not ");
|
|
error += value->ob_type->tp_name;
|
|
throw Base::TypeError(error);
|
|
}
|
|
}
|
|
|
|
void PropertyFileIncluded::Save(Base::Writer& writer) const
|
|
{
|
|
// when saving a document under a new file name the transient directory
|
|
// name changes and thus the stored file name doesn't work any more.
|
|
if (!_cValue.empty() && !Base::FileInfo(_cValue).exists()) {
|
|
Base::FileInfo fi(getDocTransientPath() + "/" + _BaseFileName);
|
|
if (fi.exists()) {
|
|
_cValue = fi.filePath();
|
|
}
|
|
}
|
|
|
|
if (writer.isForceXML()) {
|
|
if (!_cValue.empty()) {
|
|
Base::FileInfo file(_cValue.c_str());
|
|
writer.Stream() << writer.ind() << "<FileIncluded data=\"" << file.fileName() << "\">"
|
|
<< std::endl;
|
|
// write the file in the XML stream
|
|
writer.incInd();
|
|
writer.insertBinFile(_cValue.c_str());
|
|
writer.decInd();
|
|
writer.Stream() << writer.ind() << "</FileIncluded>" << endl;
|
|
}
|
|
else {
|
|
writer.Stream() << writer.ind() << "<FileIncluded data=\"\"/>" << std::endl;
|
|
}
|
|
}
|
|
else {
|
|
// instead initiate an extra file
|
|
if (!_cValue.empty()) {
|
|
Base::FileInfo file(_cValue.c_str());
|
|
std::string filename = writer.addFile(file.fileName().c_str(), this);
|
|
filename = encodeAttribute(filename);
|
|
writer.Stream() << writer.ind() << "<FileIncluded file=\"" << filename << "\"/>"
|
|
<< std::endl;
|
|
}
|
|
else {
|
|
writer.Stream() << writer.ind() << "<FileIncluded file=\"\"/>" << std::endl;
|
|
}
|
|
}
|
|
}
|
|
|
|
void PropertyFileIncluded::Restore(Base::XMLReader& reader)
|
|
{
|
|
reader.readElement("FileIncluded");
|
|
if (reader.hasAttribute("file")) {
|
|
string file(reader.getAttribute("file"));
|
|
if (!file.empty()) {
|
|
// initiate a file read
|
|
reader.addFile(file.c_str(), this);
|
|
// is in the document transient path
|
|
aboutToSetValue();
|
|
_cValue = getDocTransientPath() + "/" + file;
|
|
_BaseFileName = file;
|
|
hasSetValue();
|
|
}
|
|
}
|
|
// section is XML stream
|
|
else if (reader.hasAttribute("data")) {
|
|
string file(reader.getAttribute("data"));
|
|
if (!file.empty()) {
|
|
// is in the document transient path
|
|
aboutToSetValue();
|
|
_cValue = getDocTransientPath() + "/" + file;
|
|
reader.readBinFile(_cValue.c_str());
|
|
reader.readEndElement("FileIncluded");
|
|
_BaseFileName = file;
|
|
// set read-only after restoring the file
|
|
Base::FileInfo fi(_cValue.c_str());
|
|
fi.setPermissions(Base::FileInfo::ReadOnly);
|
|
hasSetValue();
|
|
}
|
|
}
|
|
}
|
|
|
|
void PropertyFileIncluded::SaveDocFile(Base::Writer& writer) const
|
|
{
|
|
Base::ifstream from(Base::FileInfo(_cValue.c_str()), std::ios::in | std::ios::binary);
|
|
if (!from) {
|
|
std::stringstream str;
|
|
str << "PropertyFileIncluded::SaveDocFile(): "
|
|
<< "File '" << _cValue << "' in transient directory doesn't exist.";
|
|
throw Base::FileSystemError(str.str());
|
|
}
|
|
|
|
// copy plain data
|
|
unsigned char c;
|
|
std::ostream& to = writer.Stream();
|
|
while (from.get((char&)c)) {
|
|
to.put((char)c);
|
|
}
|
|
}
|
|
|
|
void PropertyFileIncluded::RestoreDocFile(Base::Reader& reader)
|
|
{
|
|
Base::FileInfo fi(_cValue.c_str());
|
|
if (fi.exists() && !fi.isWritable()) {
|
|
// This happens when an object is being restored and tries to reference the
|
|
// same file of another object (e.g. for copy&paste of objects inside the same document).
|
|
return;
|
|
}
|
|
Base::ofstream to(fi, std::ios::out | std::ios::binary);
|
|
if (!to) {
|
|
std::stringstream str;
|
|
str << "PropertyFileIncluded::RestoreDocFile(): "
|
|
<< "File '" << _cValue << "' in transient directory cannot be created.";
|
|
throw Base::FileSystemError(str.str());
|
|
}
|
|
|
|
// copy plain data
|
|
aboutToSetValue();
|
|
unsigned char c;
|
|
while (reader.get((char&)c)) {
|
|
to.put((char)c);
|
|
}
|
|
to.close();
|
|
|
|
// set read-only after restoring the file
|
|
fi.setPermissions(Base::FileInfo::ReadOnly);
|
|
hasSetValue();
|
|
}
|
|
|
|
Property* PropertyFileIncluded::Copy() const
|
|
{
|
|
std::unique_ptr<PropertyFileIncluded> prop(new PropertyFileIncluded());
|
|
|
|
// remember the base name
|
|
prop->_BaseFileName = _BaseFileName;
|
|
|
|
Base::FileInfo file(_cValue);
|
|
if (file.exists()) {
|
|
// create a new name in the document transient directory
|
|
Base::FileInfo newName(getUniqueFileName(file.dirPath(), file.fileName()));
|
|
if (this->StatusBits.test(10)) {
|
|
// rename the file
|
|
bool done = file.renameFile(newName.filePath().c_str());
|
|
if (!done) {
|
|
std::stringstream str;
|
|
str << "PropertyFileIncluded::Copy(): "
|
|
<< "Renaming the file '" << file.filePath() << "' to '" << newName.filePath()
|
|
<< "' failed.";
|
|
throw Base::FileSystemError(str.str());
|
|
}
|
|
}
|
|
else {
|
|
// copy the file
|
|
bool done = file.copyTo(newName.filePath().c_str());
|
|
if (!done) {
|
|
std::stringstream str;
|
|
str << "PropertyFileIncluded::Copy(): "
|
|
<< "Copying the file '" << file.filePath() << "' to '" << newName.filePath()
|
|
<< "' failed.";
|
|
throw Base::FileSystemError(str.str());
|
|
}
|
|
}
|
|
|
|
// remember the new name for the Undo
|
|
Base::Console().Log("Copy '%s' to '%s'\n", _cValue.c_str(), newName.filePath().c_str());
|
|
prop->_cValue = newName.filePath().c_str();
|
|
|
|
// make backup files writable to avoid copying them again on undo/redo
|
|
newName.setPermissions(Base::FileInfo::ReadWrite);
|
|
}
|
|
|
|
return prop.release();
|
|
}
|
|
|
|
void PropertyFileIncluded::Paste(const Property& from)
|
|
{
|
|
aboutToSetValue();
|
|
const PropertyFileIncluded& prop = dynamic_cast<const PropertyFileIncluded&>(from);
|
|
// make sure that source and destination file are different
|
|
if (_cValue != prop._cValue) {
|
|
// delete old file (if still there)
|
|
Base::FileInfo fi(_cValue);
|
|
fi.setPermissions(Base::FileInfo::ReadWrite);
|
|
fi.deleteFile();
|
|
|
|
// get path to destination which can be the transient directory
|
|
// of another document
|
|
std::string pathTrans = getDocTransientPath();
|
|
Base::FileInfo fiSrc(prop._cValue);
|
|
Base::FileInfo fiDst(pathTrans + "/" + prop._BaseFileName);
|
|
std::string path = fiSrc.dirPath();
|
|
|
|
if (fiSrc.exists()) {
|
|
fiDst.setFile(getUniqueFileName(fiDst.dirPath(), fiDst.fileName()));
|
|
|
|
// if the file is already in transient dir of the document, just use it
|
|
if (path == pathTrans) {
|
|
if (!fiSrc.renameFile(fiDst.filePath().c_str())) {
|
|
std::stringstream str;
|
|
str << "PropertyFileIncluded::Paste(): "
|
|
<< "Renaming the file '" << fiSrc.filePath() << "' to '" << fiDst.filePath()
|
|
<< "' failed.";
|
|
throw Base::FileSystemError(str.str());
|
|
}
|
|
}
|
|
else {
|
|
if (!fiSrc.copyTo(fiDst.filePath().c_str())) {
|
|
std::stringstream str;
|
|
str << "PropertyFileIncluded::Paste(): "
|
|
<< "Copying the file '" << fiSrc.filePath() << "' to '" << fiDst.filePath()
|
|
<< "' failed.";
|
|
throw Base::FileSystemError(str.str());
|
|
}
|
|
}
|
|
|
|
// set the file again read-only
|
|
fiDst.setPermissions(Base::FileInfo::ReadOnly);
|
|
_cValue = fiDst.filePath();
|
|
}
|
|
else {
|
|
_cValue.clear();
|
|
}
|
|
|
|
// set the base name
|
|
_BaseFileName = prop._BaseFileName;
|
|
}
|
|
hasSetValue();
|
|
}
|
|
|
|
unsigned int PropertyFileIncluded::getMemSize() const
|
|
{
|
|
unsigned int mem = Property::getMemSize();
|
|
mem += _cValue.size();
|
|
mem += _BaseFileName.size();
|
|
return mem;
|
|
}
|
|
|
|
void PropertyFileIncluded::setFilter(std::string filter)
|
|
{
|
|
m_filter = std::move(filter);
|
|
}
|
|
|
|
std::string PropertyFileIncluded::getFilter() const
|
|
{
|
|
return m_filter;
|
|
}
|
|
|
|
//**************************************************************************
|
|
// PropertyFile
|
|
//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
|
|
|
TYPESYSTEM_SOURCE(App::PropertyFile, App::PropertyString)
|
|
|
|
PropertyFile::PropertyFile()
|
|
{
|
|
m_filter = "";
|
|
}
|
|
|
|
PropertyFile::~PropertyFile() = default;
|
|
|
|
void PropertyFile::setFilter(const std::string f)
|
|
{
|
|
m_filter = f;
|
|
}
|
|
|
|
std::string PropertyFile::getFilter() const
|
|
{
|
|
return m_filter;
|
|
}
|
|
|
|
void PropertyFile::setPyObject(PyObject* value)
|
|
{
|
|
if (PyDict_Check(value)) {
|
|
Py::Dict dict(value);
|
|
if (dict.hasKey("filter")) {
|
|
setFilter(Py::String(dict.getItem("filter")));
|
|
}
|
|
if (dict.hasKey("filename")) {
|
|
std::string string = static_cast<std::string>(Py::String(dict.getItem("filename")));
|
|
setValue(string.c_str());
|
|
}
|
|
}
|
|
else {
|
|
PropertyString::setPyObject(value);
|
|
}
|
|
}
|