1// Copyright (C) 2021 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
4#include "qqmldomfilewriter_p.h"
5
6#include <QtCore/qrandom.h>
7#include <QtCore/qscopeguard.h>
8#include <QtCore/qtextstream.h>
9
10QT_BEGIN_NAMESPACE
11namespace QQmlJS {
12namespace Dom {
13
14FileWriter::Status FileWriter::write(const QString &tFile, function_ref<bool(QTextStream &)> write,
15 int nBk)
16{
17 if (shouldRemoveTempFile)
18 tempFile.remove();
19 tempFile.close();
20 shouldRemoveTempFile = false;
21 Q_ASSERT(status != Status::ShouldWrite);
22 status = Status::ShouldWrite;
23 targetFile = tFile;
24 newBkFiles.clear();
25 warnings.clear();
26
27 int i = 0;
28 const int maxAttempts = 20;
29 for (; i < maxAttempts; ++i) {
30 tempFile.setFileName(targetFile
31 + QString::number(QRandomGenerator::global()->generate(), base: 16).mid(position: 0, n: 8)
32 + QStringLiteral(u".tmp"));
33 if (tempFile.open(flags: QIODevice::ReadWrite | QIODevice::NewOnly))
34 break;
35 }
36 if (i == maxAttempts) {
37 warnings.append(t: tr(sourceText: "Could not create temp file for %1").arg(a: targetFile));
38 status = FileWriter::Status::SkippedDueToFailure;
39 return status;
40 }
41 shouldRemoveTempFile = true;
42 bool success = false;
43 QTextStream inF(&tempFile);
44 QT_TRY
45 {
46 auto cleanup = qScopeGuard(f: [this, &inF, &success, nBk] {
47 inF.flush();
48 tempFile.flush();
49 tempFile.close();
50 if (success) {
51 if (QFile::exists(fileName: targetFile)) {
52 // compareFiles
53 if (tempFile.open(flags: QIODevice::ReadOnly)) {
54 auto closeTmpF = qScopeGuard(f: [this] { tempFile.close(); });
55 QFile oldF(targetFile);
56 if (oldF.open(flags: QIODevice::ReadOnly)) {
57 bool same = true;
58 while (!tempFile.atEnd() && !oldF.atEnd()) {
59 QByteArray l1 = tempFile.readLine();
60 QByteArray l2 = oldF.readLine();
61 if (l1 != l2)
62 same = false;
63 }
64 if (tempFile.atEnd() && oldF.atEnd() && same) {
65 tempFile.remove();
66 shouldRemoveTempFile = false;
67 status = Status::SkippedEqual;
68 return;
69 }
70 }
71 }
72 }
73 // move to target
74 int i = 0;
75 const int maxAttempts = 10;
76 for (; i < maxAttempts; ++i) {
77 if (QFile::exists(fileName: targetFile)) {
78 // make place for targetFile
79 QString bkFileName;
80 if (nBk < 1) {
81 QFile::remove(fileName: targetFile);
82 } else if (nBk == 1) {
83 QString bkFileName = targetFile + QStringLiteral(u"~");
84 QFile::remove(fileName: bkFileName);
85 QFile::rename(oldName: targetFile, newName: bkFileName);
86 } else {
87 // f~ is the oldest, further backups at f1~ .. f<nBk>~
88 // keeping an empty place for the "next" backup
89 // f~ is never overwritten
90 int iBk = 0;
91 QString bkFileName = targetFile + QStringLiteral(u"~");
92 while (++iBk < nBk) {
93 if (QFile::exists(fileName: bkFileName))
94 bkFileName = targetFile + QString::number(iBk)
95 + QStringLiteral(u"~");
96 }
97 if (iBk == nBk)
98 QFile::remove(fileName: targetFile + QStringLiteral(u"1~"));
99 else
100 QFile::remove(fileName: targetFile + QString::number(++iBk)
101 + QStringLiteral(u"~"));
102 QFile::remove(fileName: bkFileName);
103 QFile::rename(oldName: targetFile, newName: bkFileName);
104 }
105 if (!bkFileName.isEmpty() && QFile::rename(oldName: targetFile, newName: bkFileName))
106 newBkFiles.append(t: bkFileName);
107 }
108 if (tempFile.rename(newName: targetFile)) {
109 status = Status::DidWrite;
110 shouldRemoveTempFile = false;
111 return;
112 }
113 }
114 warnings.append(
115 t: tr(sourceText: "Rename of file %1 to %2 failed").arg(args: tempFile.fileName(), args&: targetFile));
116 status = Status::SkippedDueToFailure;
117 } else {
118 warnings.append(t: tr(sourceText: "Error while writing"));
119 }
120 });
121 success = write(inF);
122 }
123 QT_CATCH(...)
124 {
125 warnings.append(t: tr(sourceText: "Exception trying to write file %1").arg(a: targetFile));
126 status = FileWriter::Status::SkippedDueToFailure;
127 }
128 if (status == Status::ShouldWrite)
129 status = Status::SkippedDueToFailure;
130 return status;
131}
132
133} // namespace Dom
134} // namespace QQmlJS
135QT_END_NAMESPACE
136
137#include "moc_qqmldomfilewriter_p.cpp"
138

source code of qtdeclarative/src/qmldom/qqmldomfilewriter.cpp