FreeCAD

Форк
0
/
DocumentRecovery.cpp 
746 строк · 25.8 Кб
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
// Implement FileWriter which puts files into a directory
24
// write a property to file only when it has been modified
25
// implement xml meta file
26

27
#include "PreCompiled.h"
28

29
#ifndef _PreComp_
30
# include <boost/interprocess/sync/file_lock.hpp>
31
# include <QApplication>
32
# include <QCloseEvent>
33
# include <QDateTime>
34
# include <QDebug>
35
# include <QDir>
36
# include <QDomDocument>
37
# include <QFileInfo>
38
# include <QHeaderView>
39
# include <QList>
40
# include <QMap>
41
# include <QMenu>
42
# include <QMessageBox>
43
# include <QSet>
44
# include <QTextStream>
45
# include <QTreeWidgetItem>
46
# include <QVector>
47
# include <sstream>
48
#endif
49

50
#include <App/Application.h>
51
#include <App/Document.h>
52
#include <Base/Exception.h>
53
#include <Gui/Application.h>
54
#include <Gui/Command.h>
55
#include <Gui/DlgCheckableMessageBox.h>
56
#include <Gui/Document.h>
57
#include <Gui/MainWindow.h>
58

59
#include "DocumentRecovery.h"
60
#include "ui_DocumentRecovery.h"
61
#include "WaitCursor.h"
62

63

64
FC_LOG_LEVEL_INIT("Gui", true, true)
65

66
using namespace Gui;
67
using namespace Gui::Dialog;
68
namespace sp = std::placeholders;
69

70
// taken from the script doctools.py
71
std::string DocumentRecovery::doctools =
72
"import os,sys,string\n"
73
"import xml.sax\n"
74
"import xml.sax.handler\n"
75
"import xml.sax.xmlreader\n"
76
"import zipfile\n"
77
"\n"
78
"# SAX handler to parse the Document.xml\n"
79
"class DocumentHandler(xml.sax.handler.ContentHandler):\n"
80
"	def __init__(self, dirname):\n"
81
"		self.files = []\n"
82
"		self.dirname = dirname\n"
83
"\n"
84
"	def startElement(self, name, attributes):\n"
85
"		if name == 'XLink':\n"
86
"			return\n"
87
"		item=attributes.get(\"file\")\n"
88
"		if item:\n"
89
"			self.files.append(os.path.join(self.dirname,str(item)))\n"
90
"\n"
91
"	def characters(self, data):\n"
92
"		return\n"
93
"\n"
94
"	def endElement(self, name):\n"
95
"		return\n"
96
"\n"
97
"def extractDocument(filename, outpath):\n"
98
"	zfile=zipfile.ZipFile(filename)\n"
99
"	files=zfile.namelist()\n"
100
"\n"
101
"	for i in files:\n"
102
"		data=zfile.read(i)\n"
103
"		dirs=i.split(\"/\")\n"
104
"		if len(dirs) > 1:\n"
105
"			dirs.pop()\n"
106
"			curpath=outpath\n"
107
"			for j in dirs:\n"
108
"				curpath=curpath+\"/\"+j\n"
109
"				os.mkdir(curpath)\n"
110
"		output=open(outpath+\"/\"+i,\'wb\')\n"
111
"		output.write(data)\n"
112
"		output.close()\n"
113
"\n"
114
"def createDocument(filename, outpath):\n"
115
"	files=getFilesList(filename)\n"
116
"	dirname=os.path.dirname(filename)\n"
117
"	guixml=os.path.join(dirname,\"GuiDocument.xml\")\n"
118
"	if os.path.exists(guixml):\n"
119
"		files.extend(getFilesList(guixml))\n"
120
"	compress=zipfile.ZipFile(outpath,\'w\',zipfile.ZIP_DEFLATED)\n"
121
"	for i in files:\n"
122
"		dirs=os.path.split(i)\n"
123
"		#print i, dirs[-1]\n"
124
"		compress.write(i,dirs[-1],zipfile.ZIP_DEFLATED)\n"
125
"	compress.close()\n"
126
"\n"
127
"def getFilesList(filename):\n"
128
"	dirname=os.path.dirname(filename)\n"
129
"	handler=DocumentHandler(dirname)\n"
130
"	parser=xml.sax.make_parser()\n"
131
"	parser.setContentHandler(handler)\n"
132
"	parser.parse(filename)\n"
133
"\n"
134
"	files=[]\n"
135
"	files.append(filename)\n"
136
"	files.extend(iter(handler.files))\n"
137
"	return files\n"
138
;
139

140

141
namespace Gui { namespace Dialog {
142
class DocumentRecoveryPrivate
143
{
144
public:
145
    using XmlConfig = QMap<QString, QString>;
146

147
    enum Status {
148
        Unknown = 0, /*!< The file is not available */
149
        Created = 1, /*!< The file was created but not processed so far*/
150
        Overage = 2, /*!< The recovery file is older than the actual project file */
151
        Success = 3, /*!< The file could be recovered */
152
        Failure = 4, /*!< The file could not be recovered */
153
    };
154
    struct Info {
155
        QString projectFile;
156
        QString xmlFile;
157
        QString label;
158
        QString fileName;
159
        QString tooltip;
160
        Status status = Unknown;
161
    };
162
    Ui_DocumentRecovery ui;
163
    bool recovered;
164
    QList<Info> recoveryInfo;
165

166
    Info getRecoveryInfo(const QFileInfo&) const;
167
    void writeRecoveryInfo(const Info&) const;
168
    XmlConfig readXmlFile(const QString& fn) const;
169
};
170

171
}
172
}
173

174
DocumentRecovery::DocumentRecovery(const QList<QFileInfo>& dirs, QWidget* parent)
175
  : QDialog(parent), d_ptr(new DocumentRecoveryPrivate())
176
{
177
    d_ptr->ui.setupUi(this);
178
    connect(d_ptr->ui.buttonCleanup, &QPushButton::clicked,
179
            this, &DocumentRecovery::onButtonCleanupClicked);
180
    d_ptr->ui.buttonBox->button(QDialogButtonBox::Ok)->setText(tr("Start Recovery"));
181
    d_ptr->ui.treeWidget->header()->setSectionResizeMode(QHeaderView::Stretch);
182

183
    d_ptr->recovered = false;
184

185
    for (QList<QFileInfo>::const_iterator it = dirs.begin(); it != dirs.end(); ++it) {
186
        DocumentRecoveryPrivate::Info info = d_ptr->getRecoveryInfo(*it);
187

188
        if (info.status == DocumentRecoveryPrivate::Created) {
189
            d_ptr->recoveryInfo << info;
190

191
            auto item = new QTreeWidgetItem(d_ptr->ui.treeWidget);
192
            item->setText(0, info.label);
193
            item->setToolTip(0, info.tooltip);
194
            item->setText(1, tr("Not yet recovered"));
195
            item->setToolTip(1, info.projectFile);
196
            d_ptr->ui.treeWidget->addTopLevelItem(item);
197
        }
198
    }
199

200
    this->adjustSize();
201
}
202

203
DocumentRecovery::~DocumentRecovery() = default;
204

205
bool DocumentRecovery::foundDocuments() const
206
{
207
    Q_D(const DocumentRecovery);
208
    return (!d->recoveryInfo.isEmpty());
209
}
210

211
QString DocumentRecovery::createProjectFile(const QString& documentXml)
212
{
213
    QString source = documentXml;
214
    QFileInfo fi(source);
215
    QString dest = fi.dir().absoluteFilePath(QString::fromLatin1("fc_recovery_file.fcstd"));
216

217
    std::stringstream str;
218
    str << doctools << "\n";
219
    str << "createDocument(\"" << (const char*)source.toUtf8()
220
        << "\", \"" << (const char*)dest.toUtf8() << "\")";
221
    Gui::Command::runCommand(Gui::Command::App, str.str().c_str());
222

223
    return dest;
224
}
225

226
void DocumentRecovery::closeEvent(QCloseEvent* e)
227
{
228
    // Do not disable the X button in the title bar
229
    // #0004281: Close Document Recovery
230
    e->accept();
231
}
232

233
void DocumentRecovery::accept()
234
{
235
    Q_D(DocumentRecovery);
236

237
    if (!d->recovered) {
238

239
        WaitCursor wc;
240
        int index = 0;
241
        std::vector<int> indices;
242
        std::vector<std::string> filenames, paths, labels, errs;
243
        for (auto &info : d->recoveryInfo) {
244
            QString errorInfo;
245
            QTreeWidgetItem* item = d_ptr->ui.treeWidget->topLevelItem(index);
246

247
            try {
248
                QString file = info.projectFile;
249
                QFileInfo fi(file);
250
                if (fi.fileName() == QLatin1String("Document.xml"))
251
                    file = createProjectFile(info.projectFile);
252

253
                paths.emplace_back(file.toUtf8().constData());
254
                filenames.emplace_back(info.fileName.toUtf8().constData());
255
                labels.emplace_back(info.label.toUtf8().constData());
256
                indices.push_back(index);
257
                ++index;
258
            }
259
            catch (const std::exception& e) {
260
                errorInfo = QString::fromLatin1(e.what());
261
            }
262
            catch (const Base::Exception& e) {
263
                errorInfo = QString::fromLatin1(e.what());
264
            }
265
            catch (...) {
266
                errorInfo = tr("Unknown problem occurred");
267
            }
268

269
            if (!errorInfo.isEmpty()) {
270
                info.status = DocumentRecoveryPrivate::Failure;
271
                if (item) {
272
                    item->setText(1, tr("Failed to recover"));
273
                    item->setToolTip(1, errorInfo);
274
                    item->setForeground(1, QColor(170,0,0));
275
                }
276
                d->writeRecoveryInfo(info);
277
            }
278
        }
279

280
        auto docs = App::GetApplication().openDocuments(filenames,&paths,&labels,&errs);
281

282
        for (size_t i = 0; i < docs.size(); ++i) {
283
            auto &info = d->recoveryInfo[indices[i]];
284
            QTreeWidgetItem* item = d_ptr->ui.treeWidget->topLevelItem(indices[i]);
285
            if (!docs[i] || !errs[i].empty()) {
286
                if (docs[i])
287
                    App::GetApplication().closeDocument(docs[i]->getName());
288
                info.status = DocumentRecoveryPrivate::Failure;
289

290
                if (item) {
291
                    item->setText(1, tr("Failed to recover"));
292
                    item->setToolTip(1, QString::fromUtf8(errs[i].c_str()));
293
                    item->setForeground(1, QColor(170,0,0));
294
                }
295
                // write back current status
296
                d->writeRecoveryInfo(info);
297
            }
298
            else {
299
                auto gdoc = Application::Instance->getDocument(docs[i]);
300
                if (gdoc)
301
                    gdoc->setModified(true);
302

303
                info.status = DocumentRecoveryPrivate::Success;
304
                if (item) {
305
                    item->setText(1, tr("Successfully recovered"));
306
                    item->setForeground(1, QColor(0,170,0));
307
                }
308

309
                QDir transDir(QString::fromUtf8(docs[i]->TransientDir.getValue()));
310

311
                QFileInfo xfi(info.xmlFile);
312
                QFileInfo fi(info.projectFile);
313
                bool res = false;
314

315
                if (fi.fileName() == QLatin1String("fc_recovery_file.fcstd")) {
316
                    transDir.remove(fi.fileName());
317
                    res = transDir.rename(fi.absoluteFilePath(),fi.fileName());
318
                }
319
                else {
320
                    transDir.rmdir(fi.dir().dirName());
321
                    res = transDir.rename(fi.absolutePath(),fi.dir().dirName());
322
                }
323

324
                if (res) {
325
                    transDir.remove(xfi.fileName());
326
                    res = transDir.rename(xfi.absoluteFilePath(),xfi.fileName());
327
                }
328

329
                if (!res) {
330
                    FC_WARN("Failed to move recovery file of document '"
331
                            << docs[i]->Label.getValue() << "'");
332
                }
333
                else {
334
                    DocumentRecoveryCleaner().clearDirectory(QFileInfo(xfi.absolutePath()));
335
                    QDir().rmdir(xfi.absolutePath());
336
                }
337

338
                // DO NOT write success into recovery info, in case the program
339
                // crash again before the user save the just recovered file.
340
            }
341
        }
342

343
        d->ui.buttonBox->button(QDialogButtonBox::Ok)->setText(tr("Finish"));
344
        d->ui.buttonBox->button(QDialogButtonBox::Cancel)->setEnabled(false);
345
        d->recovered = true;
346
    }
347
    else {
348
        QDialog::accept();
349
    }
350
}
351

352
void DocumentRecoveryPrivate::writeRecoveryInfo(const DocumentRecoveryPrivate::Info& info) const
353
{
354
    // Write recovery meta file
355
    QFile file(info.xmlFile);
356
    if (file.open(QFile::WriteOnly)) {
357
        QTextStream str(&file);
358
#if QT_VERSION < QT_VERSION_CHECK(6,0,0)
359
        str.setCodec("UTF-8");
360
#endif
361
        str << "<?xml version='1.0' encoding='utf-8'?>\n"
362
            << "<AutoRecovery SchemaVersion=\"1\">\n";
363
        switch (info.status) {
364
        case Created:
365
            str << "  <Status>Created</Status>\n";
366
            break;
367
        case Overage:
368
            str << "  <Status>Deprecated</Status>\n";
369
            break;
370
        case Success:
371
            str << "  <Status>Success</Status>\n";
372
            break;
373
        case Failure:
374
            str << "  <Status>Failure</Status>\n";
375
            break;
376
        default:
377
            str << "  <Status>Unknown</Status>\n";
378
            break;
379
        }
380
        str << "  <Label>" << info.label << "</Label>\n";
381
        str << "  <FileName>" << info.fileName << "</FileName>\n";
382
        str << "</AutoRecovery>\n";
383
        file.close();
384
    }
385
}
386

387
DocumentRecoveryPrivate::Info DocumentRecoveryPrivate::getRecoveryInfo(const QFileInfo& fi) const
388
{
389
    DocumentRecoveryPrivate::Info info;
390
    info.status = DocumentRecoveryPrivate::Unknown;
391
    info.label = qApp->translate("StdCmdNew","Unnamed");
392

393
    QString file;
394
    QDir doc_dir(fi.absoluteFilePath());
395
    QDir rec_dir(doc_dir.absoluteFilePath(QLatin1String("fc_recovery_files")));
396

397
    // compressed recovery file
398
    if (doc_dir.exists(QLatin1String("fc_recovery_file.fcstd"))) {
399
        file = doc_dir.absoluteFilePath(QLatin1String("fc_recovery_file.fcstd"));
400
    }
401
    // separate files for recovery
402
    else if (rec_dir.exists(QLatin1String("Document.xml"))) {
403
        file = rec_dir.absoluteFilePath(QLatin1String("Document.xml"));
404
    }
405

406
    info.status = DocumentRecoveryPrivate::Created;
407
    info.projectFile = file;
408
    info.tooltip = fi.fileName();
409

410
    // when the Xml meta exists get some relevant information
411
    info.xmlFile = doc_dir.absoluteFilePath(QLatin1String("fc_recovery_file.xml"));
412
    if (doc_dir.exists(QLatin1String("fc_recovery_file.xml"))) {
413
        XmlConfig cfg = readXmlFile(info.xmlFile);
414

415
        if (cfg.contains(QString::fromLatin1("Label"))) {
416
            info.label = cfg[QString::fromLatin1("Label")];
417
        }
418

419
        if (cfg.contains(QString::fromLatin1("FileName"))) {
420
            info.fileName = cfg[QString::fromLatin1("FileName")];
421
        }
422

423
        if (cfg.contains(QString::fromLatin1("Status"))) {
424
            QString status = cfg[QString::fromLatin1("Status")];
425
            if (status == QLatin1String("Deprecated"))
426
                info.status = DocumentRecoveryPrivate::Overage;
427
            else if (status == QLatin1String("Success"))
428
                info.status = DocumentRecoveryPrivate::Success;
429
            else if (status == QLatin1String("Failure"))
430
                info.status = DocumentRecoveryPrivate::Failure;
431
        }
432

433
        if (info.status == DocumentRecoveryPrivate::Created) {
434
            // compare the modification dates
435
            QFileInfo fileInfo(info.fileName);
436
            if (!info.fileName.isEmpty() && fileInfo.exists()) {
437
                QDateTime dateRecv = QFileInfo(file).lastModified();
438
                QDateTime dateProj = fileInfo.lastModified();
439
                if (dateRecv < dateProj) {
440
                    info.status = DocumentRecoveryPrivate::Overage;
441
                    writeRecoveryInfo(info);
442
                    qWarning() << "Ignore recovery file " << file.toUtf8()
443
                        << " because it is older than the project file" << info.fileName.toUtf8() << "\n";
444
                }
445
            }
446
        }
447
    }
448

449
    return info;
450
}
451

452
DocumentRecoveryPrivate::XmlConfig DocumentRecoveryPrivate::readXmlFile(const QString& fn) const
453
{
454
    DocumentRecoveryPrivate::XmlConfig cfg;
455
    QDomDocument domDocument;
456
    QFile file(fn);
457
    if (!file.open(QFile::ReadOnly))
458
        return cfg;
459

460
    QString errorStr;
461
    int errorLine;
462
    int errorColumn;
463

464
    if (!domDocument.setContent(&file, true, &errorStr, &errorLine,
465
                                &errorColumn)) {
466
        return cfg;
467
    }
468

469
    QDomElement root = domDocument.documentElement();
470
    if (root.tagName() != QLatin1String("AutoRecovery")) {
471
        return cfg;
472
    }
473

474
    file.close();
475

476
    QVector<QString> filter;
477
    filter << QString::fromLatin1("Label");
478
    filter << QString::fromLatin1("FileName");
479
    filter << QString::fromLatin1("Status");
480

481
    QDomElement child;
482
    if (!root.isNull()) {
483
        child = root.firstChildElement();
484
        while (!child.isNull()) {
485
            QString name = child.localName();
486
            QString value = child.text();
487
            if (std::find(filter.begin(), filter.end(), name) != filter.end())
488
                cfg[name] = value;
489
            child = child.nextSiblingElement();
490
        }
491
    }
492

493
    return cfg;
494
}
495

496
void DocumentRecovery::contextMenuEvent(QContextMenuEvent* ev)
497
{
498
    QList<QTreeWidgetItem*> items = d_ptr->ui.treeWidget->selectedItems();
499
    if (!items.isEmpty()) {
500
        QMenu menu;
501
        menu.addAction(tr("Delete"), this, &DocumentRecovery::onDeleteSection);
502
        menu.exec(ev->globalPos());
503
    }
504
}
505

506
void DocumentRecovery::onDeleteSection()
507
{
508
    QMessageBox msgBox(this);
509
    msgBox.setIcon(QMessageBox::Warning);
510
    msgBox.setWindowTitle(tr("Cleanup"));
511
    msgBox.setText(tr("Are you sure you want to delete the selected transient directories?"));
512
    msgBox.setInformativeText(tr("When deleting the selected transient directory you won't be able to recover any files afterwards."));
513
    msgBox.setStandardButtons(QMessageBox::Yes | QMessageBox::No);
514
    msgBox.setDefaultButton(QMessageBox::No);
515
    int ret = msgBox.exec();
516
    if (ret == QMessageBox::No)
517
        return;
518

519
    QList<QTreeWidgetItem*> items = d_ptr->ui.treeWidget->selectedItems();
520
    QDir tmp = QString::fromUtf8(App::Application::getUserCachePath().c_str());
521
    for (QList<QTreeWidgetItem*>::iterator it = items.begin(); it != items.end(); ++it) {
522
        int index = d_ptr->ui.treeWidget->indexOfTopLevelItem(*it);
523
        QTreeWidgetItem* item = d_ptr->ui.treeWidget->takeTopLevelItem(index);
524

525
        QString projectFile = item->toolTip(0);
526
        DocumentRecoveryCleaner().clearDirectory(QFileInfo(tmp.filePath(projectFile)));
527
        tmp.rmdir(projectFile);
528
        delete item;
529
    }
530

531
    int numItems = d_ptr->ui.treeWidget->topLevelItemCount();
532
    if (numItems == 0) {
533
        d_ptr->ui.buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false);
534
        d_ptr->ui.buttonBox->button(QDialogButtonBox::Cancel)->setEnabled(true);
535
    }
536
}
537

538
void DocumentRecovery::onButtonCleanupClicked()
539
{
540
    QMessageBox msgBox(this);
541
    msgBox.setIcon(QMessageBox::Warning);
542
    msgBox.setWindowTitle(tr("Cleanup"));
543
    msgBox.setText(tr("Are you sure you want to delete all transient directories?"));
544
    msgBox.setInformativeText(tr("When deleting all transient directories you won't be able to recover any files afterwards."));
545
    msgBox.setStandardButtons(QMessageBox::Yes | QMessageBox::No);
546
    msgBox.setDefaultButton(QMessageBox::No);
547
    int ret = msgBox.exec();
548
    if (ret == QMessageBox::No)
549
        return;
550

551
    d_ptr->ui.treeWidget->clear();
552
    d_ptr->ui.buttonCleanup->setEnabled(false);
553
    d_ptr->ui.buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false);
554
    d_ptr->ui.buttonBox->button(QDialogButtonBox::Cancel)->setEnabled(true);
555

556
    DocumentRecoveryHandler handler;
557
    handler.checkForPreviousCrashes(std::bind(&DocumentRecovery::cleanup, this, sp::_1, sp::_2, sp::_3));
558
    DlgCheckableMessageBox::showMessage(tr("Delete"), tr("Transient directories deleted."));
559
    reject();
560
}
561

562
void DocumentRecovery::cleanup(QDir& tmp, const QList<QFileInfo>& dirs, const QString& lockFile)
563
{
564
    if (!dirs.isEmpty()) {
565
        for (QList<QFileInfo>::const_iterator jt = dirs.cbegin(); jt != dirs.cend(); ++jt) {
566
            DocumentRecoveryCleaner().clearDirectory(*jt);
567
            tmp.rmdir(jt->fileName());
568
        }
569
    }
570
    tmp.remove(lockFile);
571
}
572

573
// ----------------------------------------------------------------------------
574

575
bool DocumentRecoveryFinder::checkForPreviousCrashes()
576
{
577
    //NOLINTBEGIN
578
    DocumentRecoveryHandler handler;
579
    handler.checkForPreviousCrashes(std::bind(&DocumentRecoveryFinder::checkDocumentDirs, this, sp::_1, sp::_2, sp::_3));
580
    //NOLINTEND
581

582
    return showRecoveryDialogIfNeeded();
583
}
584

585
void DocumentRecoveryFinder::checkDocumentDirs(QDir& tmp, const QList<QFileInfo>& dirs, const QString& fn)
586
{
587
    if (dirs.isEmpty()) {
588
        // delete the lock file immediately if no transient directories are related
589
        tmp.remove(fn);
590
    }
591
    else {
592
        int countDeletedDocs = 0;
593
        QString recovery_files = QString::fromLatin1("fc_recovery_files");
594
        for (QList<QFileInfo>::const_iterator it = dirs.cbegin(); it != dirs.cend(); ++it) {
595
            QDir doc_dir(it->absoluteFilePath());
596
            doc_dir.setFilter(QDir::NoDotAndDotDot|QDir::AllEntries);
597
            uint entries = doc_dir.entryList().count();
598
            if (entries == 0) {
599
                // in this case we can delete the transient directory because
600
                // we cannot do anything
601
                if (tmp.rmdir(it->filePath()))
602
                    countDeletedDocs++;
603
            }
604
            // search for the existence of a recovery file
605
            else if (doc_dir.exists(QLatin1String("fc_recovery_file.xml"))) {
606
                // store the transient directory in case it's not empty
607
                restoreDocFiles << *it;
608
            }
609
            // search for the 'fc_recovery_files' sub-directory and check that it's the only entry
610
            else if (entries == 1 && doc_dir.exists(recovery_files)) {
611
                // if the sub-directory is empty delete the transient directory
612
                QDir rec_dir(doc_dir.absoluteFilePath(recovery_files));
613
                rec_dir.setFilter(QDir::NoDotAndDotDot|QDir::AllEntries);
614
                if (rec_dir.entryList().isEmpty()) {
615
                    doc_dir.rmdir(recovery_files);
616
                    if (tmp.rmdir(it->filePath()))
617
                        countDeletedDocs++;
618
                }
619
            }
620
        }
621

622
        // all directories corresponding to the lock file have been deleted
623
        // so delete the lock file, too
624
        if (countDeletedDocs == dirs.size()) {
625
            tmp.remove(fn);
626
        }
627
    }
628
}
629

630
bool DocumentRecoveryFinder::showRecoveryDialogIfNeeded()
631
{
632
    bool foundRecoveryFiles = false;
633
    if (!restoreDocFiles.isEmpty()) {
634
        Gui::Dialog::DocumentRecovery dlg(restoreDocFiles, Gui::getMainWindow());
635
        if (dlg.foundDocuments()) {
636
            foundRecoveryFiles = true;
637
            dlg.exec();
638
        }
639
    }
640

641
    return foundRecoveryFiles;
642
}
643

644
// ----------------------------------------------------------------------------
645

646
void DocumentRecoveryHandler::checkForPreviousCrashes(const std::function<void(QDir&, const QList<QFileInfo>&, const QString&)> & callableFunc) const
647
{
648
    QDir tmp = QString::fromUtf8(App::Application::getUserCachePath().c_str());
649
    tmp.setNameFilters(QStringList() << QString::fromLatin1("*.lock"));
650
    tmp.setFilter(QDir::Files);
651

652
    QString exeName = QString::fromStdString(App::Application::getExecutableName());
653
    QList<QFileInfo> locks = tmp.entryInfoList();
654
    for (QList<QFileInfo>::iterator it = locks.begin(); it != locks.end(); ++it) {
655
        QString bn = it->baseName();
656
        // ignore the lock file for this instance
657
        QString pid = QString::number(QCoreApplication::applicationPid());
658
        if (bn.startsWith(exeName) && bn.indexOf(pid) < 0) {
659
            QString fn = it->absoluteFilePath();
660

661
#if !defined(FC_OS_WIN32) || (BOOST_VERSION < 107600)
662
            boost::interprocess::file_lock flock(fn.toUtf8());
663
#else
664
            boost::interprocess::file_lock flock(fn.toStdWString().c_str());
665
#endif
666
            if (flock.try_lock()) {
667
                // OK, this file is a leftover from a previous crash
668
                QString crashed_pid = bn.mid(exeName.length()+1);
669
                // search for transient directories with this PID
670
                QString filter;
671
                QTextStream str(&filter);
672
                str << exeName << "_Doc_*_" << crashed_pid;
673
                tmp.setNameFilters(QStringList() << filter);
674
                tmp.setFilter(QDir::Dirs);
675
                QList<QFileInfo> dirs = tmp.entryInfoList();
676

677
                callableFunc(tmp, dirs, it->fileName());
678
            }
679
        }
680
    }
681
}
682

683
// ----------------------------------------------------------------------------
684

685
void DocumentRecoveryCleaner::clearDirectory(const QFileInfo& dir)
686
{
687
    QDir qThisDir(dir.absoluteFilePath());
688
    if (!qThisDir.exists())
689
        return;
690

691
    // Remove all files in this directory
692
    qThisDir.setFilter(QDir::Files);
693
    QStringList files = qThisDir.entryList();
694
    subtractFiles(files);
695
    for (QStringList::iterator it = files.begin(); it != files.end(); ++it) {
696
        QString file = *it;
697
        qThisDir.remove(file);
698
    }
699

700
    // Clear this directory of any sub-directories
701
    qThisDir.setFilter(QDir::Dirs | QDir::NoDotAndDotDot);
702
    QFileInfoList subdirs = qThisDir.entryInfoList();
703
    subtractDirs(subdirs);
704
    for (QFileInfoList::iterator it = subdirs.begin(); it != subdirs.end(); ++it) {
705
        clearDirectory(*it);
706
        qThisDir.rmdir(it->fileName());
707
    }
708
}
709

710
void DocumentRecoveryCleaner::subtractFiles(QStringList& files)
711
{
712
    if (!ignoreFiles.isEmpty() && !files.isEmpty()) {
713
#if QT_VERSION >= QT_VERSION_CHECK(5,14,0)
714
        auto set1 = QSet<QString>(files.begin(), files.end());
715
        auto set2 = QSet<QString>(ignoreFiles.begin(), ignoreFiles.end());
716
        set1.subtract(set2);
717
        files = QList<QString>(set1.begin(), set1.end());
718
#else
719
        QSet<QString> set1 = files.toSet();
720
        QSet<QString> set2 = ignoreFiles.toSet();
721
        set1.subtract(set2);
722
        files = set1.toList();
723
#endif
724
    }
725
}
726

727
void DocumentRecoveryCleaner::subtractDirs(QFileInfoList& dirs)
728
{
729
    if (!ignoreDirs.isEmpty() && !dirs.isEmpty()) {
730
        for (const auto& it : std::as_const(ignoreDirs)) {
731
            dirs.removeOne(it);
732
        }
733
    }
734
}
735

736
void DocumentRecoveryCleaner::setIgnoreFiles(const QStringList& list)
737
{
738
    ignoreFiles = list;
739
}
740

741
void DocumentRecoveryCleaner::setIgnoreDirectories(const QFileInfoList& list)
742
{
743
    ignoreDirs = list;
744
}
745

746
#include "moc_DocumentRecovery.cpp"
747

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

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

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

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