@@ -32,9 +32,7 @@ int Project::num_pals_total = 13;
3232
3333Project::Project (QObject *parent) :
3434 QObject(parent)
35- {
36- QObject::connect (&this ->fileWatcher , &QFileSystemWatcher::fileChanged, this , &Project::recordFileChange);
37- }
35+ { }
3836
3937Project::~Project ()
4038{
@@ -186,6 +184,7 @@ int Project::getSupportedMajorVersion(QString *errorOut) {
186184
187185bool Project::load () {
188186 this ->parser .setUpdatesSplashScreen (true );
187+ resetFileWatcher ();
189188 resetFileCache ();
190189 this ->disabledSettingsNames .clear ();
191190 bool success = readGlobalConstants ()
@@ -225,14 +224,14 @@ bool Project::load() {
225224 initNewLayoutSettings ();
226225 initNewMapSettings ();
227226 applyParsedLimits ();
227+ logFileWatchStatus ();
228228 }
229229 this ->parser .setUpdatesSplashScreen (false );
230230 return success;
231231}
232232
233233void Project::resetFileCache () {
234234 this ->parser .clearFileCache ();
235- this ->failedFileWatchPaths .clear ();
236235
237236 const QSet<QString> filepaths = {
238237 // Whenever we load a tileset we'll need to parse some data from these files, so we cache them to avoid the overhead of opening the files.
@@ -749,16 +748,21 @@ bool Project::saveMapLayouts() {
749748}
750749
751750bool Project::watchFile (const QString &filename) {
751+ if (!porymapConfig.monitorFiles )
752+ return true ;
753+
754+ if (!this ->fileWatcher ) {
755+ // Only create the file watcher when it's first needed (even an empty QFileSystemWatcher will consume system resources).
756+ this ->fileWatcher = new QFileSystemWatcher (this );
757+ QObject::connect (this ->fileWatcher , &QFileSystemWatcher::fileChanged, this , &Project::recordFileChange);
758+ }
759+
752760 QString filepath = filename.startsWith (this ->root ) ? filename : QString (" %1/%2" ).arg (this ->root ).arg (filename);
753- if (!this ->fileWatcher . addPath (filepath) && !this ->fileWatcher . files ().contains (filepath)) {
761+ if (!this ->fileWatcher -> addPath (filepath) && !this ->fileWatcher -> files ().contains (filepath)) {
754762 // We failed to watch the file, and this wasn't a file we were already watching.
755- // Log a warning, but only if A. we actually care that we failed, because 'monitor files' is enabled,
756- // B. we haven't logged a warning for this file yet, and C. we would have otherwise been able to watch it, because the file exists.
757- if (porymapConfig.monitorFiles && !this ->failedFileWatchPaths .contains (filepath) && QFileInfo::exists (filepath)) {
763+ // Record the filepath for logging later, assuming we should have been able to watch the file.
764+ if (QFileInfo::exists (filepath)) {
758765 this ->failedFileWatchPaths .insert (filepath);
759- logWarn (QString (" Failed to add '%1' to file watcher. Currently watching %2 files." )
760- .arg (Util::stripPrefix (filepath, this ->root ))
761- .arg (this ->fileWatcher .files ().length ()));
762766 }
763767 return false ;
764768 }
@@ -774,8 +778,11 @@ bool Project::watchFiles(const QStringList &filenames) {
774778}
775779
776780bool Project::stopFileWatch (const QString &filename) {
781+ if (!this ->fileWatcher )
782+ return true ;
783+
777784 QString filepath = filename.startsWith (this ->root ) ? filename : QString (" %1/%2" ).arg (this ->root ).arg (filename);
778- return this ->fileWatcher . removePath (filepath);
785+ return this ->fileWatcher -> removePath (filepath);
779786}
780787
781788void Project::ignoreWatchedFileTemporarily (const QString &filepath) {
@@ -794,8 +801,8 @@ void Project::recordFileChange(const QString &filepath) {
794801 // Note: As a safety measure, many applications save an open file by writing a new file and then deleting the old one.
795802 // In your slot function, you can check watcher.files().contains(path).
796803 // If it returns false, check whether the file still exists and then call addPath() to continue watching it.
797- if (!this ->fileWatcher . files ().contains (filepath) && QFileInfo::exists (filepath)) {
798- this ->fileWatcher . addPath (filepath);
804+ if (this -> fileWatcher && !this ->fileWatcher -> files ().contains (filepath) && QFileInfo::exists (filepath)) {
805+ this ->fileWatcher -> addPath (filepath);
799806 }
800807
801808 if (this ->modifiedFiles .contains (filepath)) {
@@ -815,6 +822,38 @@ void Project::recordFileChange(const QString &filepath) {
815822 emit fileChanged (filepath);
816823}
817824
825+ // When calling 'watchFile' we record failures rather than log them immediately.
826+ // We do this primarily to condense the warning if we fail to monitor any files.
827+ void Project::logFileWatchStatus () {
828+ if (!this ->fileWatcher )
829+ return ;
830+
831+ int numSuccessful = this ->fileWatcher ->files ().length ();
832+ int numAttempted = numSuccessful + this ->failedFileWatchPaths .count ();
833+ if (numAttempted == 0 )
834+ return ;
835+
836+ if (numSuccessful == 0 ) {
837+ // We failed to watch every file we tried. As of writing this happens if Porymap is running
838+ // on Windows and the project files are in WSL2. Rather than filling the log by
839+ // outputting a warning for every file, just log that we failed to monitor any of them.
840+ logWarn (QString (" Failed to monitor project files" ));
841+ return ;
842+ } else {
843+ logInfo (QString (" Successfully monitoring %1/%2 project files" ).arg (numSuccessful).arg (numAttempted));
844+ }
845+
846+ for (const auto &failedPath : this ->failedFileWatchPaths ) {
847+ logWarn (QString (" Failed to monitor project file '%1'" ).arg (failedPath));
848+ }
849+ }
850+
851+ void Project::resetFileWatcher () {
852+ this ->failedFileWatchPaths .clear ();
853+ delete this ->fileWatcher ;
854+ this ->fileWatcher = nullptr ;
855+ }
856+
818857bool Project::saveMapGroups () {
819858 QString mapGroupsFilepath = QString (" %1/%2" ).arg (root).arg (projectConfig.getFilePath (ProjectFilePath::json_map_groups));
820859 QFile mapGroupsFile (mapGroupsFilepath);
0 commit comments