565 lines
20 KiB
C++
565 lines
20 KiB
C++
/***************************************************************************
|
|
* (c) Jürgen Riegel (juergen.riegel@web.de) 2008 *
|
|
* *
|
|
* 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"
|
|
|
|
#ifndef _PreComp_
|
|
# include <algorithm>
|
|
# include <sstream>
|
|
#endif
|
|
|
|
/// Here the FreeCAD includes sorted by Base,App,Gui......
|
|
|
|
#include <Base/Exception.h>
|
|
#include <Base/Reader.h>
|
|
#include <Base/Writer.h>
|
|
#include <Base/Stream.h>
|
|
#include <Base/Console.h>
|
|
#include <Base/PyObjectBase.h>
|
|
#include <Base/Uuid.h>
|
|
|
|
#include "PropertyFile.h"
|
|
#include "Document.h"
|
|
#include "PropertyContainer.h"
|
|
#include "DocumentObject.h"
|
|
|
|
using namespace App;
|
|
using namespace Base;
|
|
using namespace std;
|
|
|
|
|
|
|
|
//**************************************************************************
|
|
// PropertyFileIncluded
|
|
//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
|
|
|
TYPESYSTEM_SOURCE(App::PropertyFileIncluded , App::Property);
|
|
|
|
|
|
PropertyFileIncluded::PropertyFileIncluded()
|
|
{
|
|
|
|
}
|
|
|
|
PropertyFileIncluded::~PropertyFileIncluded()
|
|
{
|
|
// clean up
|
|
if (!_cValue.empty()) {
|
|
Base::FileInfo file(_cValue.c_str());
|
|
file.setPermissions(Base::FileInfo::ReadWrite);
|
|
file.deleteFile();
|
|
}
|
|
}
|
|
|
|
void PropertyFileIncluded::aboutToSetValue(void)
|
|
{
|
|
// 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(void) const
|
|
{
|
|
std::string path;
|
|
PropertyContainer *co = getContainer();
|
|
if (co->isDerivedFrom(DocumentObject::getClassTypeId())) {
|
|
path = dynamic_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(void) const
|
|
{
|
|
return Base::FileInfo::getTempFileName(Base::FileInfo
|
|
(getValue()).fileName().c_str(), getDocTransientPath().c_str());
|
|
}
|
|
|
|
std::string PropertyFileIncluded::getOriginalFileName(void) const
|
|
{
|
|
return _OriginalName;
|
|
}
|
|
|
|
void PropertyFileIncluded::setValue(const char* sFile, const char* sName)
|
|
{
|
|
if (sFile && sFile[0] != '\0') {
|
|
if (_cValue == sFile)
|
|
throw Base::Exception("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::Exception(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(false);
|
|
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 looses 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::Exception(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(false);
|
|
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::Exception(str.str());
|
|
}
|
|
|
|
// make the file read-only
|
|
Base::FileInfo dst(_cValue);
|
|
dst.setPermissions(Base::FileInfo::ReadOnly);
|
|
}
|
|
|
|
hasSetValue();
|
|
}
|
|
}
|
|
|
|
const char* PropertyFileIncluded::getValue(void) const
|
|
{
|
|
return _cValue.c_str();
|
|
}
|
|
|
|
PyObject *PropertyFileIncluded::getPyObject(void)
|
|
{
|
|
PyObject *p = PyUnicode_DecodeUTF8(_cValue.c_str(),_cValue.size(),0);
|
|
if (!p) throw Base::Exception("PropertyFileIncluded: UTF-8 conversion failure");
|
|
return p;
|
|
}
|
|
|
|
void PropertyFileIncluded::setPyObject(PyObject *value)
|
|
{
|
|
std::string string;
|
|
if (PyUnicode_Check(value)) {
|
|
PyObject* unicode = PyUnicode_AsUTF8String(value);
|
|
string = PyString_AsString(unicode);
|
|
Py_DECREF(unicode);
|
|
}
|
|
else if (PyString_Check(value)) {
|
|
string = PyString_AsString(value);
|
|
}
|
|
else if (PyFile_Check(value)) {
|
|
PyObject* FileName = PyFile_Name(value);
|
|
string = PyString_AsString(FileName);
|
|
}
|
|
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)) {
|
|
PyObject* unicode = PyUnicode_AsUTF8String(file);
|
|
fileStr = PyString_AsString(unicode);
|
|
Py_DECREF(unicode);
|
|
}
|
|
else if (PyString_Check(file)) {
|
|
fileStr = PyString_AsString(file);
|
|
}
|
|
else if (PyFile_Check(file)) {
|
|
PyObject* FileName = PyFile_Name(file);
|
|
fileStr = PyString_AsString(FileName);
|
|
}
|
|
else {
|
|
std::string error = std::string("First item in tuple must be a file or string");
|
|
error += value->ob_type->tp_name;
|
|
throw Base::TypeError(error);
|
|
}
|
|
|
|
// decoding name
|
|
std::string nameStr;
|
|
if (PyString_Check(name)) {
|
|
nameStr = PyString_AsString(name);
|
|
}
|
|
else if (PyFile_Check(name)) {
|
|
PyObject* FileName = PyFile_Name(name);
|
|
nameStr = PyString_AsString(FileName);
|
|
}
|
|
else {
|
|
std::string error = std::string("Second item in tuple must be a string");
|
|
error += value->ob_type->tp_name;
|
|
throw Base::TypeError(error);
|
|
}
|
|
|
|
setValue(fileStr.c_str(),nameStr.c_str());
|
|
return;
|
|
}
|
|
else {
|
|
std::string error = std::string("Type must be string or file");
|
|
error += value->ob_type->tp_name;
|
|
throw Base::TypeError(error);
|
|
}
|
|
|
|
// assign the string
|
|
setValue(string.c_str());
|
|
}
|
|
|
|
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());
|
|
writer.Stream() << writer.ind() << "<FileIncluded file=\""
|
|
<< writer.addFile(file.fileName().c_str(), this) << "\"/>" << 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()) {
|
|
// initate 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::Exception(str.str());
|
|
}
|
|
|
|
// copy plain data
|
|
unsigned char c;
|
|
std::ostream& to = writer.Stream();
|
|
while (from.get((char&)c)) {
|
|
to.put((const 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::Exception(str.str());
|
|
}
|
|
|
|
// copy plain data
|
|
aboutToSetValue();
|
|
unsigned char c;
|
|
while (reader.get((char&)c)) {
|
|
to.put((const char)c);
|
|
}
|
|
to.close();
|
|
|
|
// set read-only after restoring the file
|
|
fi.setPermissions(Base::FileInfo::ReadOnly);
|
|
hasSetValue();
|
|
}
|
|
|
|
Property *PropertyFileIncluded::Copy(void) const
|
|
{
|
|
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::Exception(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::Exception(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;
|
|
}
|
|
|
|
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::Exception(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::Exception(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 (void) const
|
|
{
|
|
unsigned int mem = Property::getMemSize();
|
|
mem += _cValue.size();
|
|
mem += _BaseFileName.size();
|
|
return mem;
|
|
}
|
|
|
|
//**************************************************************************
|
|
// PropertyFile
|
|
//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
|
|
|
TYPESYSTEM_SOURCE(App::PropertyFile , App::PropertyString);
|
|
|
|
PropertyFile::PropertyFile()
|
|
{
|
|
|
|
}
|
|
|
|
PropertyFile::~PropertyFile()
|
|
{
|
|
|
|
}
|
|
|