FreeCAD

Форк
0
/
AutoSaver.cpp 
405 строк · 14.0 Кб
1
/***************************************************************************
2
 *   Copyright (c) 2015 Werner Mayer <wmayer[at]users.sourceforge.net>     *
3
 *                                                                         *
4
 *   This file is part of the FreeCAD CAx development system.              *
5
 *                                                                         *
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.      *
10
 *                                                                         *
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.                  *
15
 *                                                                         *
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                                *
20
 *                                                                         *
21
 ***************************************************************************/
22

23

24
#include "PreCompiled.h"
25

26
#ifndef _PreComp_
27
# include <QApplication>
28
# include <QFile>
29
# include <QDir>
30
# include <QRunnable>
31
# include <QTextStream>
32
# include <QThreadPool>
33
#endif
34

35
#include <App/Application.h>
36
#include <App/Document.h>
37
#include <App/DocumentObject.h>
38
#include <Base/Console.h>
39
#include <Base/FileInfo.h>
40
#include <Base/Stream.h>
41
#include <Base/TimeInfo.h>
42
#include <Base/Tools.h>
43
#include <Base/Writer.h>
44

45
#include "AutoSaver.h"
46
#include "Document.h"
47
#include "MainWindow.h"
48
#include "ViewProvider.h"
49
#include "WaitCursor.h"
50

51
FC_LOG_LEVEL_INIT("App",true,true)
52

53
using namespace Gui;
54
namespace sp = std::placeholders;
55

56
AutoSaver* AutoSaver::self = nullptr;
57
const int AutoSaveTimeout = 900000;
58

59
AutoSaver::AutoSaver(QObject* parent)
60
  : QObject(parent)
61
  , timeout(AutoSaveTimeout)
62
  , compressed(true)
63
{
64
    //NOLINTBEGIN
65
    App::GetApplication().signalNewDocument.connect(std::bind(&AutoSaver::slotCreateDocument, this, sp::_1));
66
    App::GetApplication().signalDeleteDocument.connect(std::bind(&AutoSaver::slotDeleteDocument, this, sp::_1));
67
    //NOLINTEND
68
}
69

70
AutoSaver::~AutoSaver() = default;
71

72
AutoSaver* AutoSaver::instance()
73
{
74
    if (!self) {
75
        self = new AutoSaver(QApplication::instance());
76
    }
77
    return self;
78
}
79

80
void AutoSaver::renameFile(QString dirName, QString file, QString tmpFile)
81
{
82
    FC_LOG("auto saver rename " << tmpFile.toUtf8().constData()
83
            << " -> " << file.toUtf8().constData());
84
    QDir dir(dirName);
85
    dir.remove(file);
86
    dir.rename(tmpFile,file);
87
}
88

89
void AutoSaver::setTimeout(int ms)
90
{
91
    timeout = Base::clamp<int>(ms, 0, 3600000); // between 0 and 60 min
92

93
    // go through the attached documents and apply the new timeout
94
    for (auto & it : saverMap) {
95
        if (it.second->timerId > 0)
96
            killTimer(it.second->timerId);
97
        int id = timeout > 0 ? startTimer(timeout) : 0;
98
        it.second->timerId = id;
99
    }
100
}
101

102
void AutoSaver::setCompressed(bool on)
103
{
104
    this->compressed = on;
105
}
106

107
void AutoSaver::slotCreateDocument(const App::Document& Doc)
108
{
109
    std::string name = Doc.getName();
110
    int id = timeout > 0 ? startTimer(timeout) : 0;
111
    AutoSaveProperty* as = new AutoSaveProperty(&Doc);
112
    as->timerId = id;
113

114
    if (!this->compressed) {
115
        std::string dirName = Doc.TransientDir.getValue();
116
        dirName += "/fc_recovery_files";
117
        Base::FileInfo fi(dirName);
118
        fi.createDirectory();
119
        as->dirName = dirName;
120
    }
121
    saverMap.insert(std::make_pair(name, as));
122
}
123

124
void AutoSaver::slotDeleteDocument(const App::Document& Doc)
125
{
126
    std::string name = Doc.getName();
127
    std::map<std::string, AutoSaveProperty*>::iterator it = saverMap.find(name);
128
    if (it != saverMap.end()) {
129
        if (it->second->timerId > 0)
130
            killTimer(it->second->timerId);
131
        delete it->second;
132
        saverMap.erase(it);
133
    }
134
}
135

136
void AutoSaver::saveDocument(const std::string& name, AutoSaveProperty& saver)
137
{
138
    Gui::WaitCursor wc;
139
    App::Document* doc = App::GetApplication().getDocument(name.c_str());
140
    if (doc && !doc->testStatus(App::Document::PartialDoc)
141
            && !doc->testStatus(App::Document::TempDoc))
142
    {
143
        // Set the document's current transient directory
144
        std::string dirName = doc->TransientDir.getValue();
145
        dirName += "/fc_recovery_files";
146
        saver.dirName = dirName;
147

148
        // Write recovery meta file
149
        QFile file(QString::fromLatin1("%1/fc_recovery_file.xml")
150
            .arg(QString::fromUtf8(doc->TransientDir.getValue())));
151
        if (file.open(QFile::WriteOnly)) {
152
            QTextStream str(&file);
153
#if QT_VERSION < QT_VERSION_CHECK(6,0,0)
154
            str.setCodec("UTF-8");
155
#endif
156
            str << "<?xml version='1.0' encoding='utf-8'?>\n"
157
                << "<AutoRecovery SchemaVersion=\"1\">\n";
158
            str << "  <Status>Created</Status>\n";
159
            str << "  <Label>" << QString::fromUtf8(doc->Label.getValue()) << "</Label>\n"; // store the document's current label
160
            str << "  <FileName>" << QString::fromUtf8(doc->FileName.getValue()) << "</FileName>\n"; // store the document's current filename
161
            str << "</AutoRecovery>\n";
162
            file.close();
163
        }
164

165
        // make sure to tmp. disable saving thumbnails because this causes trouble if the
166
        // associated 3d view is not active
167
        Base::Reference<ParameterGrp> hGrp = App::GetApplication().GetParameterGroupByPath
168
            ("User parameter:BaseApp/Preferences/Document");
169
        bool save = hGrp->GetBool("SaveThumbnail",true);
170
        hGrp->SetBool("SaveThumbnail",false);
171

172
        getMainWindow()->showMessage(tr("Please wait until the AutoRecovery file has been saved..."), 5000);
173
        //qApp->processEvents();
174

175
        Base::TimeElapsed startTime;
176
        // open extra scope to close ZipWriter properly
177
        {
178
            if (!this->compressed) {
179
                RecoveryWriter writer(saver);
180

181
                // We will be using thread pool if not compressed.
182
                // So, always force binary format because ASCII
183
                // is not reentrant. See PropertyPartShape::SaveDocFile
184
                writer.setMode("BinaryBrep");
185

186
                writer.putNextEntry("Document.xml");
187

188
                doc->Save(writer);
189

190
                // Special handling for Gui document.
191
                doc->signalSaveDocument(writer);
192

193
                // write additional files
194
                writer.writeFiles();
195
            }
196
            // only create the file if something has changed
197
            else if (!saver.touched.empty()) {
198
                std::string fn = doc->TransientDir.getValue();
199
                fn += "/fc_recovery_file.fcstd";
200
                Base::FileInfo tmp(fn);
201
                Base::ofstream file(tmp, std::ios::out | std::ios::binary);
202
                if (file.is_open())
203
                {
204
                    Base::ZipWriter writer(file);
205
                    if (hGrp->GetBool("SaveBinaryBrep", true))
206
                        writer.setMode("BinaryBrep");
207

208
                    writer.setComment("AutoRecovery file");
209
                    writer.setLevel(1); // apparently the fastest compression
210
                    writer.putNextEntry("Document.xml");
211

212
                    doc->Save(writer);
213

214
                    // Special handling for Gui document.
215
                    doc->signalSaveDocument(writer);
216

217
                    // write additional files
218
                    writer.writeFiles();
219
                }
220
            }
221
        }
222

223
        Base::Console().Log("Save AutoRecovery file in %fs\n", Base::TimeElapsed::diffTimeF(startTime,Base::TimeElapsed()));
224
        hGrp->SetBool("SaveThumbnail",save);
225
    }
226
}
227

228
void AutoSaver::timerEvent(QTimerEvent * event)
229
{
230
    int id = event->timerId();
231
    for (auto & it : saverMap) {
232
        if (it.second->timerId == id) {
233
            try {
234
                saveDocument(it.first, *it.second);
235
                it.second->touched.clear();
236
                break;
237
            }
238
            catch (...) {
239
                Base::Console().Error("Failed to auto-save document '%s'\n", it.first.c_str());
240
            }
241
        }
242
    }
243
}
244

245
// ----------------------------------------------------------------------------
246

247
AutoSaveProperty::AutoSaveProperty(const App::Document* doc) : timerId(-1)
248
{
249
    //NOLINTBEGIN
250
    documentNew = const_cast<App::Document*>(doc)->signalNewObject.connect
251
        (std::bind(&AutoSaveProperty::slotNewObject, this, sp::_1));
252
    documentMod = const_cast<App::Document*>(doc)->signalChangedObject.connect
253
        (std::bind(&AutoSaveProperty::slotChangePropertyData, this, sp::_2));
254
    //NOLINTEND
255
}
256

257
AutoSaveProperty::~AutoSaveProperty()
258
{
259
    documentNew.disconnect();
260
    documentMod.disconnect();
261
}
262

263
void AutoSaveProperty::slotNewObject(const App::DocumentObject& obj)
264
{
265
    std::vector<App::Property*> props;
266
    obj.getPropertyList(props);
267

268
    // if an object was deleted and then restored by an undo then add all properties
269
    // because this might be the data files which we may want to re-write
270
    for (const auto & prop : props) {
271
        slotChangePropertyData(*prop);
272
    }
273
}
274

275
void AutoSaveProperty::slotChangePropertyData(const App::Property& prop)
276
{
277
    std::stringstream str;
278
    str << static_cast<const void *>(&prop) << std::ends;
279
    std::string address = str.str();
280
    this->touched.insert(address);
281
}
282

283
// ----------------------------------------------------------------------------
284

285
RecoveryWriter::RecoveryWriter(AutoSaveProperty& saver)
286
  : Base::FileWriter(saver.dirName.c_str()), saver(saver)
287
{
288
}
289

290
RecoveryWriter::~RecoveryWriter() = default;
291

292
bool RecoveryWriter::shouldWrite(const std::string& name, const Base::Persistence *object) const
293
{
294
    // Property files of a view provider can always be written because
295
    // these are rather small files.
296
    if (object->isDerivedFrom(App::Property::getClassTypeId())) {
297
        const auto* prop = static_cast<const App::Property*>(object);
298
        const App::PropertyContainer* parent = prop->getContainer();
299
        if (parent && parent->isDerivedFrom(Gui::ViewProvider::getClassTypeId()))
300
            return true;
301
    }
302
    else if (object->isDerivedFrom(Gui::Document::getClassTypeId())) {
303
        return true;
304
    }
305

306
    // These are the addresses of touched properties of a document object.
307
    std::stringstream str;
308
    str << static_cast<const void *>(object) << std::ends;
309
    std::string address = str.str();
310

311
    // Check if the property will be exported to the same file. If the file has changed or if the property hasn't been
312
    // yet exported then (re-)write the file.
313
    std::map<std::string, std::string>::iterator it = saver.fileMap.find(address);
314
    if (it == saver.fileMap.end() || it->second != name) {
315
        saver.fileMap[address] = name;
316
        return true;
317
    }
318

319
    std::set<std::string>::const_iterator jt = saver.touched.find(address);
320
    return (jt != saver.touched.end());
321
}
322

323
namespace Gui {
324

325
class RecoveryRunnable : public QRunnable
326
{
327
public:
328
    RecoveryRunnable(const std::set<std::string>& modes, const char* dir, const char* file, const App::Property* p)
329
        : prop(p->Copy())
330
        , writer(dir)
331
    {
332
        writer.setModes(modes);
333

334
        dirName = QString::fromUtf8(dir);
335
        fileName = QString::fromUtf8(file);
336
        tmpName = QString::fromLatin1("%1.tmp%2").arg(fileName).arg(rand());
337
        writer.putNextEntry(tmpName.toUtf8().constData());
338
    }
339
    ~RecoveryRunnable() override
340
    {
341
        delete prop;
342
    }
343
    void run() override
344
    {
345
        prop->SaveDocFile(writer);
346
        writer.close();
347

348
        // We could have renamed the file in this thread. However, there is
349
        // still chance of crash when we deleted the original and before rename
350
        // the new file. So we ask the main thread to do it. There is still
351
        // possibility of crash caused by thread other than the main, but
352
        // that's the best we can do for now.
353
        QMetaObject::invokeMethod(AutoSaver::instance(), "renameFile",
354
                Qt::QueuedConnection, Q_ARG(QString,dirName)
355
                ,Q_ARG(QString,fileName),Q_ARG(QString,tmpName));
356
    }
357

358
private:
359
    App::Property* prop;
360
    Base::FileWriter writer;
361
    QString dirName;
362
    QString fileName;
363
    QString tmpName;
364
};
365

366
}
367

368
void RecoveryWriter::writeFiles()
369
{
370
    // use a while loop because it is possible that while
371
    // processing the files new ones can be added
372
    size_t index = 0;
373
    this->FileStream.close();
374
    while (index < FileList.size()) {
375
        FileEntry entry = FileList.begin()[index];
376

377
        if (shouldWrite(entry.FileName, entry.Object)) {
378
            std::string filePath = entry.FileName;
379
            std::string::size_type pos = 0;
380
            while ((pos = filePath.find('/', pos)) != std::string::npos) {
381
                std::string dirName = DirName + "/" + filePath.substr(0, pos);
382
                pos++;
383
                Base::FileInfo fi(dirName);
384
                fi.createDirectory();
385
            }
386

387
            // For properties a copy can be created and then this can be written to disk in a thread
388
            if (entry.Object->isDerivedFrom(App::Property::getClassTypeId())) {
389
                const auto* prop = static_cast<const App::Property*>(entry.Object);
390
                QThreadPool::globalInstance()->start(new RecoveryRunnable(getModes(), DirName.c_str(), entry.FileName.c_str(), prop));
391
            }
392
            else {
393
                std::string fileName = DirName + "/" + entry.FileName;
394
                this->FileStream.open(fileName.c_str(), std::ios::out | std::ios::binary);
395
                entry.Object->SaveDocFile(*this);
396
                this->FileStream.close();
397
            }
398
        }
399

400
        index++;
401
    }
402
}
403

404

405
#include "moc_AutoSaver.cpp"
406

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.