Skip to content

Commit 68c4abe

Browse files
committed
Updated Release v1.9.0
1 parent 0b91d0a commit 68c4abe

File tree

6 files changed

+217
-71
lines changed

6 files changed

+217
-71
lines changed

components/libComponents/include/components/FileChangeListenerImpl.h

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ class FileChangeListenerImpl {
4545
struct Node {
4646
Node(
4747
utils::u8path path = {}, Node *parent = nullptr, bool isDirectory = true, bool didFileExistOnLastWatch = false)
48-
: parent_(parent), isDirectory_(isDirectory), didFileExistOnLastWatch_(didFileExistOnLastWatch) {
48+
: path_(path), parent_(parent), isDirectory_(isDirectory), didFileExistOnLastWatch_(didFileExistOnLastWatch) {
4949
}
5050

5151
utils::u8path path_;
@@ -78,6 +78,9 @@ class FileChangeListenerImpl {
7878
void onDirectoryChanged(const QString &dirPath);
7979
Node *createDirectoryWatches(const raco::utils::u8path &path);
8080
void removeDirectoryWatches(Node* node);
81+
Node *getNode(const raco::utils::u8path &path);
82+
void updateDirectoryWatches(Node *node);
83+
8184

8285
static bool fileCanBeAccessed(const raco::utils::u8path &path);
8386
};

components/libComponents/src/FileChangeListenerImpl.cpp

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,27 @@ FileChangeListenerImpl::Node* FileChangeListenerImpl::createDirectoryWatches(con
9191
return &rootNode_;
9292
}
9393

94+
FileChangeListenerImpl::Node* FileChangeListenerImpl::getNode(const raco::utils::u8path& path) {
95+
if (path != path.root_path()) {
96+
auto node = getNode(path.parent_path());
97+
98+
auto it = node->children_.find(path);
99+
assert(it != node->children_.end());
100+
return it->second.get();
101+
}
102+
return &rootNode_;
103+
}
104+
105+
106+
void FileChangeListenerImpl::updateDirectoryWatches(Node* node) {
107+
if (node->isDirectory_ && node->path_.existsDirectory()) {
108+
addPathToWatch(QString::fromStdString(node->path_.string()));
109+
for (const auto& [dummy, childNode] : node->children_) {
110+
updateDirectoryWatches(childNode.get());
111+
}
112+
}
113+
}
114+
94115
void FileChangeListenerImpl::remove(const std::string& absPath) {
95116
const raco::utils::u8path& path(absPath);
96117
if (fileWatcher_.removePath(QString::fromStdString(path.string()))) {
@@ -147,7 +168,9 @@ void FileChangeListenerImpl::onDelayedLoad() {
147168

148169
void FileChangeListenerImpl::onDirectoryChanged(const QString& dirPathString) {
149170
utils::u8path dirPath(dirPathString.toStdString());
150-
std::vector<utils::u8path> updated;
171+
if (auto node = getNode(dirPath)) {
172+
updateDirectoryWatches(node);
173+
}
151174
for (const auto& [path, entry] : watchedFiles_) {
152175
if (dirPath.contains(path)) {
153176
if (path.exists() != entry->didFileExistOnLastWatch_) {

components/libComponents/tests/FileChangeMonitor_test.cpp

Lines changed: 164 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,48 @@
1414
#include "components/FileChangeMonitorImpl.h"
1515
#include "testing/TestEnvironmentCore.h"
1616
#include "utils/u8path.h"
17+
#include "utils/FileUtils.h"
1718

1819
#include <fstream>
1920

2021
using namespace raco::core;
2122

22-
class FileChangeMonitorTest : public TestEnvironmentCore {
23+
class BasicFileChangeMonitorTest : public TestEnvironmentCore {
24+
protected:
25+
bool waitForFileChangeCounterGEq(int count, int timeOutInMS = 2000) {
26+
auto start = std::chrono::steady_clock::now();
27+
do {
28+
// Programmatic file/directory modifications need to be explicitly processed in a Qt event loop (QCoreApplication).
29+
30+
// Briefly yield, so the QFileSystemWatcher has time to react to our file changes and
31+
// add its event to the event loop.
32+
std::this_thread::sleep_for(std::chrono::milliseconds(1));
33+
// Process the event the QFileSystemWatcher queued. This will cause the FileChangeListener
34+
// to create a timer.
35+
QCoreApplication::processEvents();
36+
37+
// Wait for the timer to queue its event, and then process the timer event, which eventually
38+
// leads to the callbacks registered with FileMonitor::registerFileChangedHandler to be called.
39+
std::this_thread::sleep_for(std::chrono::milliseconds(raco::components::FileChangeListenerImpl::DELAYED_FILE_LOAD_TIME_MSEC + 100));
40+
QCoreApplication::processEvents();
41+
}
42+
while (fileChangeCounter_ < count && std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now() - start).count() <= timeOutInMS);
43+
44+
return fileChangeCounter_ == count;
45+
}
46+
47+
int argc = 0;
48+
// Apparently QCoreApplication needs to be initialized before the ProjectFileChangeMonitor is created, since that
49+
// will create signal connections which don't work on Linux otherwise (but they do work on Windows).
50+
QCoreApplication eventLoop_{argc, nullptr};
51+
52+
int fileChangeCounter_{0};
53+
std::function<void(void)> testCallback_ = [this]() { ++fileChangeCounter_; };
54+
std::unique_ptr<raco::components::ProjectFileChangeMonitor> testFileChangeMonitor_ = std::make_unique<raco::components::ProjectFileChangeMonitor>();
55+
std::vector<raco::components::ProjectFileChangeMonitor::UniqueListener> createdFileListeners_;
56+
};
57+
58+
class FileChangeMonitorTest : public BasicFileChangeMonitorTest {
2359
protected:
2460
static constexpr const char* TEST_RESOURCES_FOLDER_NAME = "testresources";
2561
static constexpr const char* TEST_FILE_NAME = "test.txt";
@@ -28,8 +64,7 @@ class FileChangeMonitorTest : public TestEnvironmentCore {
2864
TestEnvironmentCore::SetUp();
2965
std::filesystem::create_directory(testFolderPath_);
3066

31-
testFileOutputStream_ = std::ofstream(testFilePath_.string(), std::ios_base::out);
32-
testFileOutputStream_.close();
67+
raco::utils::file::write(testFilePath_, {});
3368

3469
createdFileListeners_.emplace_back(testFileChangeMonitor_->registerFileChangedHandler(testFilePath_.string(), testCallback_));
3570
}
@@ -47,28 +82,6 @@ class FileChangeMonitorTest : public TestEnvironmentCore {
4782
TestEnvironmentCore::TearDown();
4883
}
4984

50-
int waitForFileChangeCounterGEq(int count, int timeOutInMS = 2000) {
51-
auto start = std::chrono::steady_clock::now();
52-
do {
53-
// Programmatic file/directory modifications need to be explicitly processed in a Qt event loop (QCoreApplication).
54-
55-
// Briefly yield, so the QFileSystemWatcher has time to react to our file changes and
56-
// add its event to the event loop.
57-
std::this_thread::sleep_for(std::chrono::milliseconds(1));
58-
// Process the event the QFileSystemWatcher queued. This will cause the FileChangeListener
59-
// to create a timer.
60-
QCoreApplication::processEvents();
61-
62-
// Wait for the timer to queue its event, and then process the timer event, which eventually
63-
// leads to the callbacks registered with FileMonitor::registerFileChangedHandler to be called.
64-
std::this_thread::sleep_for(std::chrono::milliseconds(raco::components::FileChangeListenerImpl::DELAYED_FILE_LOAD_TIME_MSEC + 100));
65-
QCoreApplication::processEvents();
66-
}
67-
while (fileChangeCounter_ < count && std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now() - start).count() <= timeOutInMS);
68-
69-
return fileChangeCounter_;
70-
}
71-
7285
void runRenamingRoutine(raco::utils::u8path& originPath, const char* firstRename, const char* secondRename) {
7386
auto newFilePath = originPath;
7487
newFilePath.replace_filename(firstRename);
@@ -77,62 +90,47 @@ class FileChangeMonitorTest : public TestEnvironmentCore {
7790
evenNewerFilePath.replace_filename(secondRename);
7891

7992
std::filesystem::rename(testFilePath_, newFilePath);
80-
ASSERT_EQ(waitForFileChangeCounterGEq(1), 1);
93+
ASSERT_TRUE(waitForFileChangeCounterGEq(1));
8194

8295
std::filesystem::rename(newFilePath, evenNewerFilePath);
83-
ASSERT_EQ(waitForFileChangeCounterGEq(1), 1);
96+
ASSERT_TRUE(waitForFileChangeCounterGEq(1));
8497

8598
std::filesystem::rename(evenNewerFilePath, testFilePath_);
86-
ASSERT_EQ(waitForFileChangeCounterGEq(2), 2);
99+
ASSERT_TRUE(waitForFileChangeCounterGEq(2));
87100

88101
std::filesystem::rename(testFilePath_, newFilePath);
89-
ASSERT_EQ(waitForFileChangeCounterGEq(3), 3);
102+
ASSERT_TRUE(waitForFileChangeCounterGEq(3));
90103
}
91104

92-
int argc = 0;
93-
// Apparently QCoreApplication needs to be initialized before the ProjectFileChangeMonitor is created, since that
94-
// will create signal connections which don't work on Linux otherwise (but they do work on Windows).
95-
QCoreApplication eventLoop_{argc, nullptr};
96-
std::ofstream testFileOutputStream_;
97-
int fileChangeCounter_{0};
98105
raco::utils::u8path testFolderPath_{test_path().append(TEST_RESOURCES_FOLDER_NAME)};
99106
raco::utils::u8path testFilePath_{raco::utils::u8path(testFolderPath_).append(TEST_FILE_NAME)};
100-
std::function<void(void)> testCallback_ = [this]() { ++fileChangeCounter_; };
101-
std::unique_ptr<raco::components::ProjectFileChangeMonitor> testFileChangeMonitor_ = std::make_unique<raco::components::ProjectFileChangeMonitor>();
102-
std::vector<raco::components::ProjectFileChangeMonitor::UniqueListener> createdFileListeners_;
103107
};
104108

105-
106109
TEST_F(FileChangeMonitorTest, InstantiationNoFileChange) {
107110
ASSERT_EQ(fileChangeCounter_, 0);
108111
}
109112

110113
TEST_F(FileChangeMonitorTest, FileModificationCreation) {
111114
testFilePath_ = raco::utils::u8path(testFolderPath_).append("differentFile.txt");
112115
createdFileListeners_.emplace_back(testFileChangeMonitor_->registerFileChangedHandler(testFilePath_.string(), testCallback_));
113-
ASSERT_EQ(waitForFileChangeCounterGEq(0), 0);
116+
ASSERT_TRUE(waitForFileChangeCounterGEq(0));
114117

115-
testFileOutputStream_ = std::ofstream(testFilePath_.string(), std::ios_base::out);
116-
testFileOutputStream_.close();
118+
raco::utils::file::write(testFilePath_, {});
117119

118-
ASSERT_EQ(waitForFileChangeCounterGEq(1), 1);
120+
ASSERT_TRUE(waitForFileChangeCounterGEq(1));
119121
}
120122

121123

122124
TEST_F(FileChangeMonitorTest, FileModificationEditing) {
123-
testFileOutputStream_.open(testFilePath_.string(), std::ios_base::out);
124-
125-
testFileOutputStream_ << "Test";
126-
testFileOutputStream_.flush();
127-
testFileOutputStream_.close();
125+
raco::utils::file::write(testFilePath_, "Test");
128126

129-
ASSERT_EQ(waitForFileChangeCounterGEq(1), 1);
127+
ASSERT_TRUE(waitForFileChangeCounterGEq(1));
130128
}
131129

132130

133131
TEST_F(FileChangeMonitorTest, FileModificationDeletion) {
134132
std::filesystem::remove(testFilePath_);
135-
ASSERT_EQ(waitForFileChangeCounterGEq(1), 1);
133+
ASSERT_TRUE(waitForFileChangeCounterGEq(1));
136134
}
137135

138136

@@ -142,16 +140,17 @@ TEST_F(FileChangeMonitorTest, FileModificationRenaming) {
142140

143141

144142
TEST_F(FileChangeMonitorTest, FileModificationMultipleModificationsAtTheSameTime) {
145-
testFileOutputStream_.open(testFilePath_.string(), std::ios_base::out);
146-
testFileOutputStream_ << "Test";
143+
std::ofstream testFileOutputStream(testFilePath_.string(), std::ios_base::out);
144+
testFileOutputStream << "Test";
147145

148146
std::ofstream otherOutputStream = std::ofstream(testFilePath_.string(), std::ios_base::app);
149147
otherOutputStream << "Other";
150148
otherOutputStream.close();
151149

152-
testFileOutputStream_.close();
150+
testFileOutputStream.close();
153151

154-
ASSERT_GE(waitForFileChangeCounterGEq(1), 1); // Linux gives sometimes one, sometimes two events.
152+
// Linux gives sometimes one, sometimes two events.
153+
ASSERT_TRUE(waitForFileChangeCounterGEq(1));
155154
}
156155

157156
#if (!defined(__linux))
@@ -163,22 +162,128 @@ TEST_F(FileChangeMonitorTest, FileModificationSetAndTryToEditReadOnly) {
163162
ASSERT_TRUE(!ec) << "Failed to set permissons. Error code: " << ec.value() << " Error message: '" << ec.message() << "'";
164163

165164
// fileChangeCounter_ will be 0 in WSL, but the proper value in Linux container
166-
ASSERT_EQ(waitForFileChangeCounterGEq(1), 1);
165+
ASSERT_TRUE(waitForFileChangeCounterGEq(1));
167166

168-
testFileOutputStream_.open(testFilePath_.string(), std::ios_base::out);
169-
ASSERT_FALSE(testFileOutputStream_.is_open());
167+
std::ofstream testFileOutputStream(testFilePath_.string(), std::ios_base::out);
168+
ASSERT_FALSE(testFileOutputStream.is_open());
170169

171-
ASSERT_EQ(waitForFileChangeCounterGEq(1), 1);
170+
ASSERT_TRUE(waitForFileChangeCounterGEq(1));
172171
}
173172
#endif
174173

175174

176175
TEST_F(FileChangeMonitorTest, FolderModificationDeletion) {
177176
std::filesystem::remove_all(test_path().append(TEST_RESOURCES_FOLDER_NAME));
178-
ASSERT_EQ(waitForFileChangeCounterGEq(1), 1);
177+
ASSERT_TRUE(waitForFileChangeCounterGEq(1));
179178
}
180179

181180

182181
TEST_F(FileChangeMonitorTest, FolderModificationRenaming) {
183182
runRenamingRoutine(testFolderPath_, "newFolder", "evenNewerFolder");
184183
}
184+
185+
TEST_F(BasicFileChangeMonitorTest, create_file_existing_directory) {
186+
auto testFolderPath = test_path() / "test-folder";
187+
std::string test_file_name = "test.txt";
188+
189+
std::filesystem::create_directory(testFolderPath);
190+
191+
createdFileListeners_.emplace_back(testFileChangeMonitor_->registerFileChangedHandler((testFolderPath / test_file_name).string(), testCallback_));
192+
EXPECT_EQ(fileChangeCounter_, 0);
193+
194+
raco::utils::file::write(testFolderPath / test_file_name, {});
195+
ASSERT_TRUE(waitForFileChangeCounterGEq(1));
196+
}
197+
198+
TEST_F(BasicFileChangeMonitorTest, create_file_no_directory) {
199+
auto testFolderPath = test_path() / "test-folder";
200+
std::string test_file_name = "test.txt";
201+
202+
createdFileListeners_.emplace_back(testFileChangeMonitor_->registerFileChangedHandler((testFolderPath / test_file_name).string(), testCallback_));
203+
EXPECT_EQ(fileChangeCounter_, 0);
204+
205+
std::filesystem::create_directory(testFolderPath);
206+
ASSERT_TRUE(waitForFileChangeCounterGEq(0));
207+
208+
raco::utils::file::write(testFolderPath / test_file_name, {});
209+
ASSERT_TRUE(waitForFileChangeCounterGEq(1));
210+
}
211+
212+
TEST_F(FileChangeMonitorTest, directory_rename_disappearance) {
213+
auto movedFolderPath = test_path() / "moved-directory";
214+
std::filesystem::rename(testFolderPath_, movedFolderPath);
215+
ASSERT_TRUE(waitForFileChangeCounterGEq(1));
216+
217+
std::filesystem::rename(movedFolderPath, testFolderPath_);
218+
ASSERT_TRUE(waitForFileChangeCounterGEq(2));
219+
}
220+
221+
TEST_F(BasicFileChangeMonitorTest, directory_rename_appearance) {
222+
auto initialFolderPath = test_path() / "initial-folder";
223+
auto testFolderPath = test_path() / "test-folder";
224+
std::string test_file_name = "test.txt";
225+
226+
std::filesystem::create_directory(initialFolderPath);
227+
raco::utils::file::write(initialFolderPath / test_file_name, {});
228+
229+
createdFileListeners_.emplace_back(testFileChangeMonitor_->registerFileChangedHandler((testFolderPath / test_file_name).string(), testCallback_));
230+
EXPECT_EQ(fileChangeCounter_, 0);
231+
232+
std::filesystem::rename(initialFolderPath, testFolderPath);
233+
ASSERT_TRUE(waitForFileChangeCounterGEq(1));
234+
}
235+
236+
TEST_F(BasicFileChangeMonitorTest, directory_appear_file_change) {
237+
auto initialFolderPath = test_path() / "initial-folder";
238+
auto testFolderPath = test_path() / "test-folder";
239+
std::string test_file_name = "test.txt";
240+
241+
std::filesystem::create_directory(initialFolderPath);
242+
raco::utils::file::write(initialFolderPath / test_file_name, {});
243+
244+
createdFileListeners_.emplace_back(testFileChangeMonitor_->registerFileChangedHandler((testFolderPath / test_file_name).string(), testCallback_));
245+
EXPECT_EQ(fileChangeCounter_, 0);
246+
247+
std::filesystem::rename(initialFolderPath, testFolderPath);
248+
ASSERT_TRUE(waitForFileChangeCounterGEq(1));
249+
250+
raco::utils::file::write(testFolderPath / test_file_name, "Test");
251+
ASSERT_TRUE(waitForFileChangeCounterGEq(2));
252+
}
253+
254+
TEST_F(BasicFileChangeMonitorTest, directory_appear_file_disappear) {
255+
auto initialFolderPath = test_path() / "initial-folder";
256+
auto testFolderPath = test_path() / "test-folder";
257+
std::string test_file_name = "test.txt";
258+
259+
std::filesystem::create_directory(initialFolderPath);
260+
raco::utils::file::write(initialFolderPath / test_file_name, {});
261+
262+
createdFileListeners_.emplace_back(testFileChangeMonitor_->registerFileChangedHandler((testFolderPath / test_file_name).string(), testCallback_));
263+
EXPECT_EQ(fileChangeCounter_, 0);
264+
265+
std::filesystem::rename(initialFolderPath, testFolderPath);
266+
ASSERT_TRUE(waitForFileChangeCounterGEq(1));
267+
268+
std::filesystem::rename(testFolderPath / test_file_name, testFolderPath / "new-file-name.txt");
269+
ASSERT_TRUE(waitForFileChangeCounterGEq(2));
270+
}
271+
272+
TEST_F(BasicFileChangeMonitorTest, directory_appear_file_appear) {
273+
auto initialFolderPath = test_path() / "initial-folder";
274+
auto testFolderPath = test_path() / "test-folder";
275+
std::string initial_test_file_name = "initial.txt";
276+
std::string test_file_name = "test.txt";
277+
278+
std::filesystem::create_directory(initialFolderPath);
279+
raco::utils::file::write(initialFolderPath / initial_test_file_name, {});
280+
281+
createdFileListeners_.emplace_back(testFileChangeMonitor_->registerFileChangedHandler((testFolderPath / test_file_name).string(), testCallback_));
282+
EXPECT_EQ(fileChangeCounter_, 0);
283+
284+
std::filesystem::rename(initialFolderPath, testFolderPath);
285+
ASSERT_TRUE(waitForFileChangeCounterGEq(0));
286+
287+
std::filesystem::rename(testFolderPath / initial_test_file_name, testFolderPath / test_file_name);
288+
ASSERT_TRUE(waitForFileChangeCounterGEq(1));
289+
}

components/libRamsesBase/include/ramses_adaptor/SceneBackend.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,8 @@ class SceneBackend : public raco::core::SceneBackendInterface {
5454
std::vector<SceneItemDesc> getSceneItemDescriptions() const override;
5555
std::string getExportedObjectNames(SEditorObject editorObject) const override;
5656

57+
static bool discardLogicEngineMessage(std::string_view message);
58+
5759
private:
5860
/**
5961
* @brief call LogicEngine validate() and filter out warnings that RamsesComposer is

0 commit comments

Comments
 (0)