1
/***************************************************************************
2
* Copyright (c) 2008 Jürgen Riegel <juergen.riegel@web.de> *
4
* This file is part of the FreeCAD CAx development system. *
6
* This library is free software; you can redistribute it and/or *
7
* modify it under the terms of the GNU Library General Public *
8
* License as published by the Free Software Foundation; either *
9
* version 2 of the License, or (at your option) any later version. *
11
* This library is distributed in the hope that it will be useful, *
12
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
13
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
14
* GNU Library General Public License for more details. *
16
* You should have received a copy of the GNU Library General Public *
17
* License along with this library; see the file COPYING.LIB. If not, *
18
* write to the Free Software Foundation, Inc., 59 Temple Place, *
19
* Suite 330, Boston, MA 02111-1307, USA *
21
***************************************************************************/
24
#include "PreCompiled.h"
26
#include <Base/Console.h>
27
#include <Base/PyObjectBase.h>
28
#include <Base/Reader.h>
29
#include <Base/Stream.h>
30
#include <Base/Writer.h>
33
#include "PropertyFile.h"
35
#include "DocumentObject.h"
36
#include "PropertyContainer.h"
45
//**************************************************************************
46
// PropertyFileIncluded
47
//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
49
TYPESYSTEM_SOURCE(App::PropertyFileIncluded , App::Property)
52
PropertyFileIncluded::PropertyFileIncluded() = default;
54
PropertyFileIncluded::~PropertyFileIncluded()
57
if (!_cValue.empty()) {
58
Base::FileInfo file(_cValue.c_str());
59
file.setPermissions(Base::FileInfo::ReadWrite);
64
void PropertyFileIncluded::aboutToSetValue()
66
// This is a trick to check in Copy() if it is called
67
// directly from outside or by the Undo/Redo mechanism.
68
// In the latter case it is sufficient to rename the file
69
// because another file will be assigned afterwards.
70
// If Copy() is directly called (e.g. to copy the file to
71
// another document) a copy of the file needs to be created.
72
// This copy will be deleted again in the class destructor.
73
this->StatusBits.set(10);
74
Property::aboutToSetValue();
75
this->StatusBits.reset(10);
78
std::string PropertyFileIncluded::getDocTransientPath() const
81
PropertyContainer *co = getContainer();
82
if (co->isDerivedFrom(DocumentObject::getClassTypeId())) {
83
path = static_cast<DocumentObject*>(co)->getDocument()->TransientDir.getValue();
84
std::replace(path.begin(), path.end(), '\\', '/');
89
std::string PropertyFileIncluded::getUniqueFileName(const std::string& path, const std::string& filename) const
92
Base::FileInfo fi(path + "/" + filename);
94
fi.setFile(path + "/" + filename + "." + uuid.getValue());
100
std::string PropertyFileIncluded::getExchangeTempFile() const
102
return Base::FileInfo::getTempFileName(Base::FileInfo
103
(getValue()).fileName().c_str(), getDocTransientPath().c_str());
106
std::string PropertyFileIncluded::getOriginalFileName() const
108
return _OriginalName;
111
void PropertyFileIncluded::setValue(const char* sFile, const char* sName)
113
if (sFile && sFile[0] != '\0') {
114
if (_cValue == sFile)
115
throw Base::FileSystemError("Not possible to set the same file!");
117
// keep the path to the original file
118
_OriginalName = sFile;
120
std::string pathTrans = getDocTransientPath();
121
Base::FileInfo file(sFile);
122
std::string path = file.dirPath();
123
if (!file.exists()) {
124
std::stringstream str;
125
str << "File " << file.filePath() << " does not exist.";
126
throw Base::FileSystemError(str.str());
129
aboutToSetValue(); // undo/redo by moving the file away with temp name
131
// remove old file (if not moved by undo)
132
Base::FileInfo value(_cValue);
133
std::string pathAct = value.dirPath();
134
if (value.exists()) {
135
value.setPermissions(Base::FileInfo::ReadWrite);
139
// if a special name given, use this instead
141
Base::FileInfo fi(pathTrans + "/" + sName);
143
// if a file with this name already exists search for a new one
144
std::string dir = pathTrans;
145
std::string fnp = fi.fileNamePure();
146
std::string ext = fi.extension();
150
std::stringstream str;
151
str << dir << "/" << fnp << i;
154
fi.setFile(str.str());
158
_cValue = fi.filePath();
159
_BaseFileName = fi.fileName();
162
_cValue = pathTrans + "/" + sName;
163
_BaseFileName = sName;
166
else if (value.fileName().empty()) {
167
_cValue = pathTrans + "/" + file.fileName();
168
_BaseFileName = file.fileName();
171
// The following applies only on files that are inside the transient
173
// When a file is read-only it is supposed to be assigned to a
174
// PropertyFileIncluded instance. In this case we must copy the
175
// file because otherwise the above instance loses its data.
176
// If the file is writable it is supposed to be of free use and
177
// it can be simply renamed.
179
// if the file is already in transient dir of the document, just use it
180
if (path == pathTrans && file.isWritable()) {
181
bool done = file.renameFile(_cValue.c_str());
183
std::stringstream str;
184
str << "Cannot rename file " << file.filePath() << " to " << _cValue;
185
throw Base::FileSystemError(str.str());
188
// make the file read-only
189
Base::FileInfo dst(_cValue);
190
dst.setPermissions(Base::FileInfo::ReadOnly);
192
// otherwise copy from origin location
194
// if file already exists in transient dir make a new unique name
195
Base::FileInfo fi(_cValue);
197
// if a file with this name already exists search for a new one
198
std::string dir = fi.dirPath();
199
std::string fnp = fi.fileNamePure();
200
std::string ext = fi.extension();
204
std::stringstream str;
205
str << dir << "/" << fnp << i;
208
fi.setFile(str.str());
212
_cValue = fi.filePath();
213
_BaseFileName = fi.fileName();
216
bool done = file.copyTo(_cValue.c_str());
218
std::stringstream str;
219
str << "Cannot copy file from " << file.filePath() << " to " << _cValue;
220
throw Base::FileSystemError(str.str());
223
// make the file read-only
224
Base::FileInfo dst(_cValue);
225
dst.setPermissions(Base::FileInfo::ReadOnly);
232
const char* PropertyFileIncluded::getValue() const
234
return _cValue.c_str();
237
PyObject *PropertyFileIncluded::getPyObject()
239
PyObject *p = PyUnicode_DecodeUTF8(_cValue.c_str(),_cValue.size(),nullptr);
241
throw Base::UnicodeError("PropertyFileIncluded: UTF-8 conversion failure");
247
const char* getNameFromFile(PyObject* value)
249
const char* string = nullptr;
250
PyObject *oname = PyObject_GetAttrString (value, "name");
252
if (PyUnicode_Check (oname)) {
253
string = PyUnicode_AsUTF8 (oname);
255
else if (PyBytes_Check (oname)) {
256
string = PyBytes_AsString (oname);
262
throw Base::TypeError("Unable to get filename");
268
bool isIOFile(PyObject* file)
270
PyObject* io = PyImport_ImportModule("io");
271
PyObject* IOBase_Class = PyObject_GetAttrString(io, "IOBase");
272
bool isFile = PyObject_IsInstance(file, IOBase_Class);
273
Py_DECREF(IOBase_Class);
279
void PropertyFileIncluded::setPyObject(PyObject *value)
281
if (PyUnicode_Check(value)) {
282
std::string string = PyUnicode_AsUTF8(value);
283
setValue(string.c_str());
285
else if (PyBytes_Check(value)) {
286
std::string string = PyBytes_AsString(value);
287
setValue(string.c_str());
289
else if (isIOFile(value)){
290
std::string string = getNameFromFile(value);
291
setValue(string.c_str());
293
else if (PyTuple_Check(value)) {
294
if (PyTuple_Size(value) != 2)
295
throw Base::TypeError("Tuple needs size of (filePath,newFileName)");
296
PyObject* file = PyTuple_GetItem(value,0);
297
PyObject* name = PyTuple_GetItem(value,1);
301
if (PyUnicode_Check(file)) {
302
fileStr = PyUnicode_AsUTF8(file);
304
else if (PyBytes_Check(file)) {
305
fileStr = PyBytes_AsString(file);
307
else if (isIOFile(value)) {
308
fileStr = getNameFromFile(file);
311
std::string error = std::string("First item in tuple must be a file or string, not ");
312
error += file->ob_type->tp_name;
313
throw Base::TypeError(error);
318
if (PyUnicode_Check(name)) {
319
nameStr = PyUnicode_AsUTF8(name);
321
else if (PyBytes_Check(name)) {
322
nameStr = PyBytes_AsString(name);
324
else if (isIOFile(value)) {
325
nameStr = getNameFromFile(name);
328
std::string error = std::string("Second item in tuple must be a string, not ");
329
error += name->ob_type->tp_name;
330
throw Base::TypeError(error);
333
setValue(fileStr.c_str(),nameStr.c_str());
335
else if (PyDict_Check(value)) {
336
Py::Dict dict(value);
337
if (dict.hasKey("filter")) {
338
setFilter(Py::String(dict.getItem("filter")));
340
if (dict.hasKey("filename")) {
341
std::string string = static_cast<std::string>(Py::String(dict.getItem("filename")));
342
setValue(string.c_str());
346
std::string error = std::string("Type must be string or file, not ");
347
error += value->ob_type->tp_name;
348
throw Base::TypeError(error);
352
void PropertyFileIncluded::Save (Base::Writer &writer) const
354
// when saving a document under a new file name the transient directory
355
// name changes and thus the stored file name doesn't work any more.
356
if (!_cValue.empty() && !Base::FileInfo(_cValue).exists()) {
357
Base::FileInfo fi(getDocTransientPath() + "/" + _BaseFileName);
359
_cValue = fi.filePath();
362
if (writer.isForceXML()) {
363
if (!_cValue.empty()) {
364
Base::FileInfo file(_cValue.c_str());
365
writer.Stream() << writer.ind() << "<FileIncluded data=\""
366
<< file.fileName() << "\">" << std::endl;
367
// write the file in the XML stream
369
writer.insertBinFile(_cValue.c_str());
371
writer.Stream() << writer.ind() <<"</FileIncluded>" << endl;
374
writer.Stream() << writer.ind() << "<FileIncluded data=\"\"/>" << std::endl;
378
// instead initiate an extra file
379
if (!_cValue.empty()) {
380
Base::FileInfo file(_cValue.c_str());
381
std::string filename = writer.addFile(file.fileName().c_str(), this);
382
filename = encodeAttribute(filename);
383
writer.Stream() << writer.ind() << "<FileIncluded file=\""
384
<< filename << "\"/>" << std::endl;
387
writer.Stream() << writer.ind() << "<FileIncluded file=\"\"/>" << std::endl;
392
void PropertyFileIncluded::Restore(Base::XMLReader &reader)
394
reader.readElement("FileIncluded");
395
if (reader.hasAttribute("file")) {
396
string file (reader.getAttribute("file") );
398
// initiate a file read
399
reader.addFile(file.c_str(),this);
400
// is in the document transient path
402
_cValue = getDocTransientPath() + "/" + file;
403
_BaseFileName = file;
407
// section is XML stream
408
else if (reader.hasAttribute("data")) {
409
string file (reader.getAttribute("data") );
411
// is in the document transient path
413
_cValue = getDocTransientPath() + "/" + file;
414
reader.readBinFile(_cValue.c_str());
415
reader.readEndElement("FileIncluded");
416
_BaseFileName = file;
417
// set read-only after restoring the file
418
Base::FileInfo fi(_cValue.c_str());
419
fi.setPermissions(Base::FileInfo::ReadOnly);
425
void PropertyFileIncluded::SaveDocFile (Base::Writer &writer) const
427
Base::ifstream from(Base::FileInfo(_cValue.c_str()), std::ios::in | std::ios::binary);
429
std::stringstream str;
430
str << "PropertyFileIncluded::SaveDocFile(): "
431
<< "File '" << _cValue << "' in transient directory doesn't exist.";
432
throw Base::FileSystemError(str.str());
437
std::ostream& to = writer.Stream();
438
while (from.get((char&)c)) {
443
void PropertyFileIncluded::RestoreDocFile(Base::Reader &reader)
445
Base::FileInfo fi(_cValue.c_str());
446
if (fi.exists() && !fi.isWritable()) {
447
// This happens when an object is being restored and tries to reference the
448
// same file of another object (e.g. for copy&paste of objects inside the same document).
451
Base::ofstream to(fi, std::ios::out | std::ios::binary);
453
std::stringstream str;
454
str << "PropertyFileIncluded::RestoreDocFile(): "
455
<< "File '" << _cValue << "' in transient directory cannot be created.";
456
throw Base::FileSystemError(str.str());
462
while (reader.get((char&)c)) {
467
// set read-only after restoring the file
468
fi.setPermissions(Base::FileInfo::ReadOnly);
472
Property *PropertyFileIncluded::Copy() const
474
std::unique_ptr<PropertyFileIncluded> prop(new PropertyFileIncluded());
476
// remember the base name
477
prop->_BaseFileName = _BaseFileName;
479
Base::FileInfo file(_cValue);
481
// create a new name in the document transient directory
482
Base::FileInfo newName(getUniqueFileName(file.dirPath(), file.fileName()));
483
if (this->StatusBits.test(10)) {
485
bool done = file.renameFile(newName.filePath().c_str());
487
std::stringstream str;
488
str << "PropertyFileIncluded::Copy(): "
489
<< "Renaming the file '" << file.filePath() << "' to '"
490
<< newName.filePath() << "' failed.";
491
throw Base::FileSystemError(str.str());
496
bool done = file.copyTo(newName.filePath().c_str());
498
std::stringstream str;
499
str << "PropertyFileIncluded::Copy(): "
500
<< "Copying the file '" << file.filePath() << "' to '"
501
<< newName.filePath() << "' failed.";
502
throw Base::FileSystemError(str.str());
506
// remember the new name for the Undo
507
Base::Console().Log("Copy '%s' to '%s'\n",_cValue.c_str(),newName.filePath().c_str());
508
prop->_cValue = newName.filePath().c_str();
510
// make backup files writable to avoid copying them again on undo/redo
511
newName.setPermissions(Base::FileInfo::ReadWrite);
514
return prop.release();
517
void PropertyFileIncluded::Paste(const Property &from)
520
const PropertyFileIncluded &prop = dynamic_cast<const PropertyFileIncluded&>(from);
521
// make sure that source and destination file are different
522
if (_cValue != prop._cValue) {
523
// delete old file (if still there)
524
Base::FileInfo fi(_cValue);
525
fi.setPermissions(Base::FileInfo::ReadWrite);
528
// get path to destination which can be the transient directory
529
// of another document
530
std::string pathTrans = getDocTransientPath();
531
Base::FileInfo fiSrc(prop._cValue);
532
Base::FileInfo fiDst(pathTrans + "/" + prop._BaseFileName);
533
std::string path = fiSrc.dirPath();
535
if (fiSrc.exists()) {
536
fiDst.setFile(getUniqueFileName(fiDst.dirPath(), fiDst.fileName()));
538
// if the file is already in transient dir of the document, just use it
539
if (path == pathTrans) {
540
if (!fiSrc.renameFile(fiDst.filePath().c_str())) {
541
std::stringstream str;
542
str << "PropertyFileIncluded::Paste(): "
543
<< "Renaming the file '" << fiSrc.filePath() << "' to '"
544
<< fiDst.filePath() << "' failed.";
545
throw Base::FileSystemError(str.str());
549
if (!fiSrc.copyTo(fiDst.filePath().c_str())) {
550
std::stringstream str;
551
str << "PropertyFileIncluded::Paste(): "
552
<< "Copying the file '" << fiSrc.filePath() << "' to '"
553
<< fiDst.filePath() << "' failed.";
554
throw Base::FileSystemError(str.str());
558
// set the file again read-only
559
fiDst.setPermissions(Base::FileInfo::ReadOnly);
560
_cValue = fiDst.filePath();
567
_BaseFileName = prop._BaseFileName;
572
unsigned int PropertyFileIncluded::getMemSize () const
574
unsigned int mem = Property::getMemSize();
575
mem += _cValue.size();
576
mem += _BaseFileName.size();
580
void PropertyFileIncluded::setFilter(std::string filter)
582
m_filter = std::move(filter);
585
std::string PropertyFileIncluded::getFilter() const
590
//**************************************************************************
592
//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
594
TYPESYSTEM_SOURCE(App::PropertyFile , App::PropertyString)
596
PropertyFile::PropertyFile()
601
PropertyFile::~PropertyFile() = default;
603
void PropertyFile::setFilter(const std::string f)
608
std::string PropertyFile::getFilter() const
613
void PropertyFile::setPyObject(PyObject *value)
615
if (PyDict_Check(value)) {
616
Py::Dict dict(value);
617
if (dict.hasKey("filter")) {
618
setFilter(Py::String(dict.getItem("filter")));
620
if (dict.hasKey("filename")) {
621
std::string string = static_cast<std::string>(Py::String(dict.getItem("filename")));
622
setValue(string.c_str());
626
PropertyString::setPyObject(value);