/*************************************************************************** * (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 # include #endif /// Here the FreeCAD includes sorted by Base,App,Gui...... #include #include #include #include #include #include #include #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(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() << "" << std::endl; // write the file in the XML stream writer.incInd(); writer.insertBinFile(_cValue.c_str()); writer.decInd(); writer.Stream() << writer.ind() <<"" << endl; } else { writer.Stream() << writer.ind() << "" << std::endl; } } else { // instead initiate an extra file if (!_cValue.empty()) { Base::FileInfo file(_cValue.c_str()); writer.Stream() << writer.ind() << "" << std::endl; } else { writer.Stream() << writer.ind() << "" << 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(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() { }