LCOV - code coverage report
Current view: top level - ezlibs - ezFile.hpp (source / functions) Coverage Total Hit
Test: Coverage (llvm-cov → lcov → genhtml) Lines: 57.5 % 619 356
Test Date: 2025-09-16 22:55:37 Functions: 62.5 % 56 35
Legend: Lines: hit not hit | Branches: + taken - not taken # not executed Branches: 58.0 % 174 101

             Branch data     Line data    Source code
       1                 :             : #pragma once
       2                 :             : 
       3                 :             : /*
       4                 :             : MIT License
       5                 :             : 
       6                 :             : Copyright (c) 2014-2024 Stephane Cuillerdier (aka aiekick)
       7                 :             : 
       8                 :             : Permission is hereby granted, free of charge, to any person obtaining a copy
       9                 :             : of this software and associated documentation files (the "Software"), to deal
      10                 :             : in the Software without restriction, including without limitation the rights
      11                 :             : to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
      12                 :             : copies of the Software, and to permit persons to whom the Software is
      13                 :             : furnished to do so, subject to the following conditions:
      14                 :             : 
      15                 :             : The above copyright notice and this permission notice shall be included in all
      16                 :             : copies or substantial portions of the Software.
      17                 :             : 
      18                 :             : THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
      19                 :             : IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
      20                 :             : FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
      21                 :             : AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
      22                 :             : LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
      23                 :             : OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
      24                 :             : SOFTWARE.
      25                 :             : */
      26                 :             : 
      27                 :             : // ezFile is part of the ezLibs project : https://github.com/aiekick/ezLibs.git
      28                 :             : 
      29                 :             : #include <map>
      30                 :             : #include <set>
      31                 :             : #include <array>
      32                 :             : #include <regex>
      33                 :             : #include <mutex>
      34                 :             : #include <ctime>
      35                 :             : #include <string>
      36                 :             : #include <vector>
      37                 :             : #include <thread>
      38                 :             : #include <atomic>
      39                 :             : #include <memory>
      40                 :             : #include <sstream>
      41                 :             : #include <fstream>
      42                 :             : #include <cstdint>
      43                 :             : #include <iterator>
      44                 :             : #include <iostream>
      45                 :             : #include <functional>
      46                 :             : #include <unordered_map>
      47                 :             : 
      48                 :             : #include "ezOS.hpp"
      49                 :             : #include "ezApp.hpp"
      50                 :             : #include "ezStr.hpp"
      51                 :             : #include "ezLog.hpp"
      52                 :             : #include <sys/stat.h>
      53                 :             : 
      54                 :             : #ifdef WINDOWS_OS
      55                 :             : #ifndef EZ_FILE_SLASH_TYPE
      56                 :             : #define EZ_FILE_SLASH_TYPE "\\"
      57                 :             : #endif  // EZ_FILE_SLASH_TYPE
      58                 :             : #include <Windows.h>
      59                 :             : #include <shellapi.h>  // ShellExecute
      60                 :             : #define stat _stat
      61                 :             : #else                // UNIX_OS
      62                 :             : #include <unistd.h>  // rmdir
      63                 :             : #ifndef EZ_FILE_SLASH_TYPE
      64                 :          54 : #define EZ_FILE_SLASH_TYPE "/"
      65                 :             : #endif  // EZ_FILE_SLASH_TYPE
      66                 :             : #ifdef APPLE_OS
      67                 :             : #include <CoreServices/CoreServices.h>
      68                 :             : #else
      69                 :             : #include <sys/inotify.h>
      70                 :             : #endif
      71                 :             : #endif
      72                 :             : 
      73                 :             : namespace ez {
      74                 :             : namespace file {
      75                 :             : 
      76                 :          10 : inline std::string loadFileToString(const std::string &vFilePathName, bool vVerbose = true) {
      77                 :          10 :     std::string ret;
      78                 :          10 :     std::ifstream docFile(vFilePathName, std::ios::in);
      79         [ +  - ]:          10 :     if (docFile.is_open()) {
      80                 :          10 :         std::stringstream strStream;
      81                 :          10 :         strStream << docFile.rdbuf();  // read the file
      82                 :          10 :         ret = strStream.str();
      83                 :          10 :         ez::str::replaceString(ret, "\r\n", "\n");
      84                 :          10 :         ez::str::replaceString(ret, "\r", "\n");
      85                 :          10 :         docFile.close();
      86                 :          10 :     } else {
      87         [ #  # ]:           0 :         if (vVerbose) {
      88                 :           0 : #ifdef EZ_TOOLS_LOG
      89                 :           0 :             LogVarError("File \"%s\" Not Found !\n", vFilePathName.c_str());
      90                 :           0 : #endif
      91                 :           0 :         }
      92                 :           0 :     }
      93                 :          10 :     return ret;
      94                 :          10 : }
      95                 :             : 
      96                 :           4 : inline bool saveStringToFile(const std::string &vDatas, const std::string &vFilePathName, bool vAddTimeStamp = false) {
      97                 :           4 :     std::string fpn = vFilePathName;
      98         [ +  - ]:           4 :     if (!fpn.empty()) {
      99         [ -  + ]:           4 :         if (vAddTimeStamp) {
     100                 :           0 :             auto dot_p = fpn.find_last_of('.');
     101                 :           0 :             time_t epoch = std::time(nullptr);
     102         [ #  # ]:           0 :             if (dot_p != std::string::npos) {
     103                 :           0 :                 fpn = fpn.substr(0, dot_p) + ez::str::toStr("_%llu", epoch) + fpn.substr(dot_p);
     104                 :           0 :             } else {
     105                 :           0 :                 fpn += ez::str::toStr("_%llu", epoch);
     106                 :           0 :             }
     107                 :           0 :         }
     108                 :           4 :         std::ofstream configFileWriter(fpn, std::ios::out);
     109         [ +  - ]:           4 :         if (!configFileWriter.bad()) {
     110                 :           4 :             configFileWriter << vDatas;
     111                 :           4 :             configFileWriter.close();
     112                 :           4 :             return true;
     113                 :           4 :         }
     114                 :           4 :     }
     115                 :           0 :     return false;
     116                 :           4 : }
     117                 :             : 
     118                 :           1 : inline std::vector<uint8_t> loadFileToBin(const std::string &vFilePathName) {
     119                 :           1 :     std::vector<uint8_t> ret;
     120                 :           1 :     std::ifstream file(vFilePathName, std::ios::binary | std::ios::ate);
     121         [ +  - ]:           1 :     if (file.is_open()) {
     122                 :           1 :         std::streamsize size = file.tellg();  // taille du fichier
     123         [ -  + ]:           1 :         if (size <= 0) {
     124                 :           0 :             return ret;
     125                 :           0 :         }
     126                 :           1 :         ret.resize(static_cast<size_t>(size));
     127                 :           1 :         file.seekg(0, std::ios::beg);
     128         [ -  + ]:           1 :         if (!file.read(reinterpret_cast<char *>(&ret[0]), size)) {
     129                 :           0 :             ret.clear();
     130                 :           0 :         }
     131                 :           1 :     }
     132                 :           1 :     return ret;
     133                 :           1 : }
     134                 :             : 
     135                 :           1 : inline bool saveBinToFile(const std::vector<uint8_t> &vDatas, const std::string &vFilePathName, bool vAddTimeStamp = false) {
     136                 :           1 :     std::string fpn = vFilePathName;
     137         [ +  - ]:           1 :     if (!fpn.empty()) {
     138         [ -  + ]:           1 :         if (vAddTimeStamp) {
     139                 :           0 :             auto dot_p = fpn.find_last_of('.');
     140                 :           0 :             time_t epoch = std::time(nullptr);
     141         [ #  # ]:           0 :             if (dot_p != std::string::npos) {
     142                 :           0 :                 fpn = fpn.substr(0, dot_p) + ez::str::toStr("_%llu", epoch) + fpn.substr(dot_p);
     143                 :           0 :             } else {
     144                 :           0 :                 fpn += ez::str::toStr("_%llu", epoch);
     145                 :           0 :             }
     146                 :           0 :         }
     147                 :           1 :         std::ofstream out(fpn, std::ios::binary | std::ios::trunc);
     148         [ +  - ]:           1 :         if (!out.bad()) {
     149                 :           1 :             out.write(reinterpret_cast<const char *>(vDatas.data()), vDatas.size());
     150                 :           1 :             out.close();
     151                 :           1 :             return true;
     152                 :           1 :         }
     153                 :           1 :     }
     154                 :           0 : 
     155                 :           0 :     return false;
     156                 :           1 : }
     157                 :             : 
     158                 :             : /* correct file path between os and different slash type between window and unix */
     159                 :          23 : inline std::string correctSlashTypeForFilePathName(const std::string &vFilePathName) {
     160                 :          23 :     std::string res = vFilePathName;
     161                 :          23 :     ez::str::replaceString(res, "\\", EZ_FILE_SLASH_TYPE);
     162                 :          23 :     ez::str::replaceString(res, "/", EZ_FILE_SLASH_TYPE);
     163                 :          23 :     return res;
     164                 :          23 : }
     165                 :             : 
     166                 :             : struct PathInfos {
     167                 :             :     std::string path;
     168                 :             :     std::string name;
     169                 :             :     std::string ext;
     170                 :             :     bool isOk = false;
     171                 :             : 
     172                 :           1 :     PathInfos() { isOk = false; }
     173                 :             : 
     174                 :           0 :     PathInfos(const std::string &vPath, const std::string &vName, const std::string &vExt) {
     175                 :           0 :         isOk = true;
     176                 :           0 :         path = vPath;
     177                 :           0 :         name = vName;
     178                 :           0 :         ext = vExt;
     179                 :           0 : 
     180                 :           0 :         if (ext.empty()) {
     181                 :           0 :             const size_t lastPoint = name.find_last_of('.');
     182                 :           0 :             if (lastPoint != std::string::npos) {
     183                 :           0 :                 ext = name.substr(lastPoint + 1);
     184                 :           0 :                 name = name.substr(0, lastPoint);
     185                 :           0 :             }
     186                 :           0 :         }
     187                 :           0 :     }
     188                 :             : 
     189                 :           0 :     std::string GetFPNE() const { return GetFPNE_WithPathNameExt(path, name, ext); }
     190                 :             : 
     191                 :           0 :     std::string GetFPNE_WithPathNameExt(std::string vPath, const std::string &vName, const std::string &vExt) const {
     192                 :           0 :         if (vPath[0] == EZ_FILE_SLASH_TYPE[0]) {
     193                 :           0 : #ifdef WINDOWS_OS
     194                 :           0 :             // if it happening on window this seem that this path msut be a relative path but with an error
     195                 :           0 :             vPath = vPath.substr(1);  // bad formated path go relative
     196                 :           0 : #endif
     197                 :           0 :         } else {
     198                 :           0 : #ifdef UNIX_OS
     199                 :           0 :             vPath = "/" + vPath;  // make it absolute
     200                 :           0 : #endif
     201                 :           0 :         }
     202                 :           0 :         std::string ext = vExt;
     203                 :           0 :         if (!ext.empty()) {
     204                 :           0 :             ext = '.' + ext;
     205                 :           0 :         }
     206                 :           0 : 
     207                 :           0 :         if (vPath.empty()) {
     208                 :           0 :             return vName + ext;
     209                 :           0 :         }
     210                 :           0 : 
     211                 :           0 :         return vPath + EZ_FILE_SLASH_TYPE + vName + ext;
     212                 :           0 :     }
     213                 :             : 
     214                 :           0 :     std::string GetFPNE_WithPath(const std::string &vPath) const { return GetFPNE_WithPathNameExt(vPath, name, ext); }
     215                 :             : 
     216                 :           0 :     std::string GetFPNE_WithPathName(const std::string &vPath, const std::string &vName) const { return GetFPNE_WithPathNameExt(vPath, vName, ext); }
     217                 :             : 
     218                 :           0 :     std::string GetFPNE_WithPathExt(const std::string &vPath, const std::string &vExt) { return GetFPNE_WithPathNameExt(vPath, name, vExt); }
     219                 :             : 
     220                 :           0 :     std::string GetFPNE_WithName(const std::string &vName) const { return GetFPNE_WithPathNameExt(path, vName, ext); }
     221                 :             : 
     222                 :           0 :     std::string GetFPNE_WithNameExt(const std::string &vName, const std::string &vExt) const { return GetFPNE_WithPathNameExt(path, vName, vExt); }
     223                 :             : 
     224                 :           0 :     std::string GetFPNE_WithExt(const std::string &vExt) const { return GetFPNE_WithPathNameExt(path, name, vExt); }
     225                 :             : };
     226                 :             : 
     227                 :           1 : inline PathInfos parsePathFileName(const std::string &vPathFileName) {
     228                 :           1 :     PathInfos res;
     229         [ +  - ]:           1 :     if (!vPathFileName.empty()) {
     230                 :           1 :         const std::string pfn = correctSlashTypeForFilePathName(vPathFileName);
     231         [ +  - ]:           1 :         if (!pfn.empty()) {
     232                 :           1 :             const size_t lastSlash = pfn.find_last_of(EZ_FILE_SLASH_TYPE);
     233         [ +  - ]:           1 :             if (lastSlash != std::string::npos) {
     234                 :           1 :                 res.name = pfn.substr(lastSlash + 1);
     235                 :           1 :                 res.path = pfn.substr(0, lastSlash);
     236                 :           1 :                 res.isOk = true;
     237                 :           1 :             }
     238                 :           1 :             const size_t lastPoint = pfn.find_last_of('.');
     239         [ +  - ]:           1 :             if (lastPoint != std::string::npos) {
     240         [ -  + ]:           1 :                 if (!res.isOk) {
     241                 :           0 :                     res.name = pfn;
     242                 :           0 :                     res.isOk = true;
     243                 :           0 :                 }
     244                 :           1 :                 res.ext = pfn.substr(lastPoint + 1);
     245                 :           1 :                 ez::str::replaceString(res.name, "." + res.ext, "");
     246                 :           1 :             }
     247         [ -  + ]:           1 :             if (!res.isOk) {
     248                 :           0 :                 res.name = pfn;
     249                 :           0 :                 res.isOk = true;
     250                 :           0 :             }
     251                 :           1 :         }
     252                 :           1 :     }
     253                 :           1 :     return res;
     254                 :           1 : }
     255                 :           1 : inline std::string simplifyFilePath(const std::string &vFilePath) {
     256                 :           1 :     std::string path = correctSlashTypeForFilePathName(vFilePath);
     257                 :           1 :     std::vector<std::string> parts;
     258                 :           1 :     std::stringstream ss(path);
     259                 :           1 :     std::string item;
     260                 :           0 : 
     261         [ +  + ]:           5 :     while (std::getline(ss, item, EZ_FILE_SLASH_TYPE[0])) {
     262 [ -  + ][ -  + ]:           4 :         if (item == "" || item == ".")
     263                 :           0 :             continue;
     264         [ +  + ]:           4 :         if (item == "..") {
     265         [ +  - ]:           1 :             if (!parts.empty())
     266                 :           1 :                 parts.pop_back();
     267                 :           0 :             // sinon on est au-dessus de root => on ignore
     268                 :           3 :         } else {
     269                 :           3 :             parts.push_back(item);
     270                 :           3 :         }
     271                 :           4 :     }
     272                 :           0 : 
     273                 :           1 :     std::string result;
     274         [ +  + ]:           3 :     for (size_t i = 0; i < parts.size(); ++i) {
     275                 :           2 :         result += parts[i];
     276         [ +  + ]:           2 :         if (i < parts.size() - 1)
     277                 :           1 :             result += EZ_FILE_SLASH_TYPE;
     278                 :           2 :     }
     279                 :           1 :     return result;
     280                 :           1 : }
     281                 :             : 
     282                 :           1 : inline std::string composePath(const std::string &vPath, const std::string &vFileName, const std::string &vExt) {
     283                 :           1 :     const auto path = correctSlashTypeForFilePathName(vPath);
     284                 :           1 :     std::string res = path;
     285         [ +  - ]:           1 :     if (!vFileName.empty()) {
     286         [ +  - ]:           1 :         if (!path.empty()) {
     287                 :           1 :             res += EZ_FILE_SLASH_TYPE;
     288                 :           1 :         }
     289                 :           1 :         res += vFileName;
     290         [ +  - ]:           1 :         if (!vExt.empty()) {
     291                 :           1 :             res += "." + vExt;
     292                 :           1 :         }
     293                 :           1 :     }
     294                 :           1 :     return res;
     295                 :           1 : }
     296                 :             : 
     297                 :           5 : inline bool isFileExist(const std::string &name) {
     298                 :           5 :     auto fileToOpen = correctSlashTypeForFilePathName(name);
     299                 :           5 :     ez::str::replaceString(fileToOpen, "\"", "");
     300                 :           5 :     ez::str::replaceString(fileToOpen, "\n", "");
     301                 :           5 :     ez::str::replaceString(fileToOpen, "\r", "");
     302                 :           5 :     std::ifstream docFile(fileToOpen, std::ios::in);
     303         [ +  + ]:           5 :     if (docFile.is_open()) {
     304                 :           4 :         docFile.close();
     305                 :           4 :         return true;
     306                 :           4 :     }
     307                 :           1 :     return false;
     308                 :           5 : }
     309                 :             : 
     310                 :           8 : inline bool isDirectoryExist(const std::string &name) {
     311         [ +  - ]:           8 :     if (!name.empty()) {
     312                 :           8 :         const std::string dir = correctSlashTypeForFilePathName(name);
     313                 :           8 :         struct stat sb;
     314         [ +  + ]:           8 :         if (stat(dir.c_str(), &sb) == 0) {
     315                 :           4 :             return (sb.st_mode & S_IFDIR);
     316                 :           4 :         }
     317                 :           8 :     }
     318                 :           4 :     return false;
     319                 :           8 : }
     320                 :             : 
     321                 :           3 : inline bool destroyFile(const std::string &vFilePathName) {
     322         [ +  - ]:           3 :     if (!vFilePathName.empty()) {
     323                 :           3 :         const auto filePathName = correctSlashTypeForFilePathName(vFilePathName);
     324         [ +  + ]:           3 :         if (isFileExist(filePathName)) {
     325         [ +  - ]:           2 :             if (remove(filePathName.c_str()) == 0) {
     326                 :           2 :                 return true;
     327                 :           2 :             }
     328                 :           2 :         }
     329                 :           3 :     }
     330                 :           1 :     return false;
     331                 :           3 : }
     332                 :             : 
     333                 :           3 : inline bool destroyDir(const std::string &vPath) {
     334         [ +  - ]:           3 :     if (!vPath.empty()) {
     335         [ +  + ]:           3 :         if (isDirectoryExist(vPath)) {
     336                 :             : #ifdef WINDOWS_OS
     337                 :             :             return (RemoveDirectoryA(vPath.c_str()) != 0);
     338                 :             : #elif defined(UNIX_OS)
     339                 :             :             return (rmdir(vPath.c_str()) == 0);
     340                 :           2 : #endif
     341                 :           2 :         }
     342                 :           3 :     }
     343                 :           1 :     return false;
     344                 :           3 : }
     345                 :             : 
     346                 :           3 : inline bool createDirectoryIfNotExist(const std::string &name) {
     347                 :           3 :     bool res = false;
     348         [ +  - ]:           3 :     if (!name.empty()) {
     349                 :           3 :         const auto filePathName = correctSlashTypeForFilePathName(name);
     350         [ +  + ]:           3 :         if (!isDirectoryExist(filePathName)) {
     351                 :           2 :             res = true;
     352                 :             : #ifdef WINDOWS_OS
     353                 :             :             if (CreateDirectory(filePathName.c_str(), nullptr) == 0) {
     354                 :             :                 res = true;
     355                 :             :             }
     356                 :             : #elif defined(UNIX_OS)
     357                 :             :             auto cmd = ez::str::toStr("mkdir -p %s", filePathName.c_str());
     358                 :           2 :             const int dir_err = std::system(cmd.c_str());
     359         [ -  + ]:           2 :             if (dir_err == -1) {
     360                 :           0 :                 LogVarError("Error creating directory %s", filePathName.c_str());
     361                 :           0 :                 res = false;
     362                 :           0 :             }
     363                 :           2 : #endif
     364                 :           2 :         }
     365                 :           3 :     }
     366                 :           3 :     return res;
     367                 :           3 : }
     368                 :             : 
     369                 :           0 : inline bool createPathIfNotExist(const std::string &vPath) {
     370                 :           0 :     bool res = false;
     371                 :           0 :     if (!vPath.empty()) {
     372                 :           0 :         auto path = correctSlashTypeForFilePathName(vPath);
     373                 :           0 :         if (!isDirectoryExist(path)) {
     374                 :           0 :             res = true;
     375                 :           0 :             ez::str::replaceString(path, "/", "|");
     376                 :           0 :             ez::str::replaceString(path, "\\", "|");
     377                 :           0 :             auto arr = ez::str::splitStringToVector(path, "|");
     378                 :           0 :             std::string fullPath;
     379                 :           0 :             for (auto it = arr.begin(); it != arr.end(); ++it) {
     380                 :           0 :                 fullPath += *it;
     381                 :           0 :                 res &= createDirectoryIfNotExist(fullPath);
     382                 :           0 :                 fullPath += EZ_FILE_SLASH_TYPE;
     383                 :           0 :             }
     384                 :           0 :         }
     385                 :           0 :     }
     386                 :           0 :     return res;
     387                 :           0 : }
     388                 :             : 
     389                 :             : // will open the file is the associated app
     390                 :           0 : inline void openFile(const std::string &vFile) {
     391                 :           0 :     const auto file = correctSlashTypeForFilePathName(vFile);
     392                 :           0 : #if defined(WINDOWS_OS)
     393                 :           0 :     auto *result = ShellExecute(nullptr, "", file.c_str(), nullptr, nullptr, SW_SHOW);
     394                 :           0 :     if (result < (HINSTANCE)32) {  //-V112
     395                 :           0 :         // try to open an editor
     396                 :           0 :         result = ShellExecute(nullptr, "edit", file.c_str(), nullptr, nullptr, SW_SHOW);
     397                 :           0 :         if (result == (HINSTANCE)SE_ERR_ASSOCINCOMPLETE || result == (HINSTANCE)SE_ERR_NOASSOC) {
     398                 :           0 :             // open associating dialog
     399                 :           0 :             const std::string sCmdOpenWith = "shell32.dll,OpenAs_RunDLL \"" + file + "\"";
     400                 :           0 :             result = ShellExecute(nullptr, "", "rundll32.exe", sCmdOpenWith.c_str(), nullptr, SW_NORMAL);
     401                 :           0 :         }
     402                 :           0 :         if (result < (HINSTANCE)32) {  // open in explorer //-V112
     403                 :           0 :             const std::string sCmdExplorer = "/select,\"" + file + "\"";
     404                 :           0 :             ShellExecute(
     405                 :           0 :                 nullptr, "", "explorer.exe", sCmdExplorer.c_str(), nullptr, SW_NORMAL);  // ce serait peut etre mieu d'utilsier la commande system comme dans SelectFile
     406                 :           0 :         }
     407                 :           0 :     }
     408                 :           0 : #elif defined(LINUX_OS)
     409                 :           0 :     int pid = fork();
     410                 :           0 :     if (pid == 0) {
     411                 :           0 :         execl("/usr/bin/xdg-open", "xdg-open", file.c_str(), (char *)0);
     412                 :           0 :     }
     413                 :           0 : #elif defined(APPLE_OS)
     414                 :           0 :     std::string cmd = "open " + file;
     415                 :           0 :     std::system(cmd.c_str());
     416                 :           0 : #endif
     417                 :           0 : }
     418                 :             : 
     419                 :             : // will open the url in the related browser
     420                 :           0 : inline void openUrl(const std::string &vUrl) {
     421                 :           0 :     const auto url = correctSlashTypeForFilePathName(vUrl);
     422                 :           0 : #ifdef WINDOWS_OS
     423                 :           0 :     ShellExecute(nullptr, nullptr, url.c_str(), nullptr, nullptr, SW_SHOW);
     424                 :           0 : #elif defined(LINUX_OS)
     425                 :           0 :     auto cmd = ez::str::toStr("<mybrowser> %s", url.c_str());
     426                 :           0 :     std::system(cmd.c_str());
     427                 :           0 : #elif defined(APPLE_OS)
     428                 :           0 :     // std::string sCmdOpenWith = "open -a Firefox " + vUrl;
     429                 :           0 :     std::string cmd = "open " + url;
     430                 :           0 :     std::system(cmd.c_str());
     431                 :           0 : #endif
     432                 :           0 : }
     433                 :             : 
     434                 :             : // will open the current file explorer and will select the file
     435                 :           0 : inline void selectFile(const std::string &vFileToSelect) {
     436                 :           0 :     const auto fileToSelect = correctSlashTypeForFilePathName(vFileToSelect);
     437                 :           0 : #ifdef WINDOWS_OS
     438                 :           0 :     if (!fileToSelect.empty()) {
     439                 :           0 :         const std::string sCmdOpenWith = "explorer /select," + fileToSelect;
     440                 :           0 :         std::system(sCmdOpenWith.c_str());
     441                 :           0 :     }
     442                 :           0 : #elif defined(LINUX_OS)
     443                 :           0 :     // todo : is there a similar cmd on linux ?
     444                 :           0 :     assert(nullptr);
     445                 :           0 : #elif defined(APPLE_OS)
     446                 :           0 :     if (!fileToSelect.empty()) {
     447                 :           0 :         std::string cmd = "open -R " + fileToSelect;
     448                 :           0 :         std::system(cmd.c_str());
     449                 :           0 :     }
     450                 :           0 : #endif
     451                 :           0 : }
     452                 :             : 
     453                 :           0 : inline std::vector<std::string> getDrives() {
     454                 :           0 :     std::vector<std::string> res;
     455                 :           0 : #ifdef WINDOWS_OS
     456                 :           0 :     const DWORD mydrives = 2048;
     457                 :           0 :     char lpBuffer[2048 + 1];
     458                 :           0 :     const DWORD countChars = GetLogicalDriveStrings(mydrives, lpBuffer);
     459                 :           0 :     if (countChars > 0) {
     460                 :           0 :         std::string var = std::string(lpBuffer, (size_t)countChars);
     461                 :           0 :         ez::str::replaceString(var, "\\", "");
     462                 :           0 :         res = ez::str::splitStringToVector(var, "\0");
     463                 :           0 :     }
     464                 :           0 : #elif defined(UNIX_OS)
     465                 :           0 :     assert(nullptr);
     466                 :           0 : #endif
     467                 :           0 :     return res;
     468                 :           0 : }
     469                 :             : 
     470                 :             : class Watcher {
     471                 :             : public:
     472                 :             :     struct PathResult {
     473                 :             :         // path are relative to rootDir
     474                 :             :         std::string rootPath;   // the watched dir
     475                 :             :         std::string oldPath;    // before rneaming
     476                 :             :         std::string newPath;    // after renaming, creation, deletion or modification
     477                 :             :         enum class ModifType {  // type of change
     478                 :             :             NONE = 0,
     479                 :             :             MODIFICATION,
     480                 :             :             CREATION,
     481                 :             :             DELETION,
     482                 :             :             RENAMED
     483                 :             :         } modifType = ModifType::NONE;
     484                 :           0 :         void clear() { *this = {}; }
     485                 :             :         // Needed for std::set, defines strict weak ordering
     486                 :           0 :         bool operator<(const PathResult &other) const {
     487         [ #  # ]:           0 :             if (newPath != other.newPath) {
     488                 :           0 :                 return newPath < other.newPath;
     489                 :           0 :             }
     490                 :           0 :             return modifType < other.modifType;
     491                 :           0 :         }
     492                 :             :     };
     493                 :             :     using Callback = std::function<void(const std::set<PathResult> &)>;
     494                 :             : 
     495                 :             : private:
     496                 :             :     std::string m_appPath;
     497                 :             :     Callback m_callback;
     498                 :             :     std::thread m_thread;
     499                 :             :     std::atomic<bool> m_running{false};
     500                 :             : 
     501                 :             :     class Pattern;
     502                 :             :     using PatternPtr = std::shared_ptr<Pattern>;
     503                 :             :     using PatternWeak = std::weak_ptr<Pattern>;
     504                 :             : 
     505                 :             :     class Pattern {
     506                 :             :     public:
     507                 :             :         enum class PatternType {  //
     508                 :             :             PATH = 0,
     509                 :             :             GLOB,  // glob is pattern with wildcard : ex toto_*_tata_*_.txt
     510                 :             :             REGEX
     511                 :             :         };
     512                 :             :         enum class PhysicalType {  //
     513                 :             :             DIR = 0,               // directory watcher
     514                 :             :             FILE                   // file watcher
     515                 :             :         };
     516                 :             : 
     517                 :             :         static std::shared_ptr<Pattern> sCreatePath(  //
     518                 :             :             const std::string &vPath,
     519                 :             :             PatternType vPatternType,
     520                 :           2 :             PhysicalType vPhysicalType) {
     521         [ +  + ]:           2 :             if (vPath.empty()) {
     522                 :           1 :                 return nullptr;
     523                 :           1 :             }
     524                 :           1 :             auto pRet = std::make_shared<Pattern>();
     525                 :           1 :             pRet->m_path = vPath;
     526                 :           1 :             pRet->m_patternType = vPatternType;
     527                 :           1 :             pRet->m_physicalType = vPhysicalType;
     528                 :           1 :             return pRet;
     529                 :           2 :         }
     530                 :             :         static std::shared_ptr<Pattern> sCreatePathFile(  //
     531                 :             :             const std::string &vRootPath,
     532                 :             :             const std::string &vFileNameExt,
     533                 :             :             PatternType vPatternType,
     534                 :           0 :             PhysicalType vPhysicalType) {
     535                 :           0 :             if (vRootPath.empty() || vFileNameExt.empty()) {
     536                 :           0 :                 return nullptr;
     537                 :           0 :             }
     538                 :           0 :             auto pRet = std::make_shared<Pattern>();
     539                 :           0 :             pRet->m_path = vRootPath;
     540                 :           0 :             pRet->m_fileNameExt = vFileNameExt;
     541                 :           0 :             pRet->m_patternType = vPatternType;
     542                 :           0 :             pRet->m_physicalType = vPhysicalType;
     543                 :           0 :             return pRet;
     544                 :           0 :         }
     545                 :             : 
     546                 :             :     private:
     547                 :             :         std::string m_path;
     548                 :             :         std::string m_fileNameExt;
     549                 :             :         PatternType m_patternType = PatternType::PATH;
     550                 :             :         PhysicalType m_physicalType = PhysicalType::DIR;
     551                 :             : 
     552                 :             :     public:
     553                 :           1 :         const std::string &getPath() const { return m_path; }
     554                 :           0 :         const std::string &getFileNameExt() const { return m_fileNameExt; }
     555                 :           0 :         PatternType getPatternType() const { return m_patternType; }
     556                 :           4 :         PhysicalType getPhysicalType() const { return m_physicalType; }
     557                 :             : 
     558                 :           0 :         bool isPatternMatch(const std::string &vPath) const {
     559         [ #  # ]:           0 :             switch (m_patternType) {
     560         [ #  # ]:           0 :                 case PatternType::PATH: {
     561         [ #  # ]:           0 :                     return (!m_fileNameExt.empty()) ? (m_fileNameExt == vPath) : (m_path == vPath);
     562                 :           0 :                 }
     563         [ #  # ]:           0 :                 case PatternType::GLOB: {
     564                 :           0 :                     return !ez::str::searchForPatternWithWildcards(vPath, m_fileNameExt).empty();
     565                 :           0 :                 }
     566         [ #  # ]:           0 :                 case PatternType::REGEX: {
     567                 :           0 :                     try {
     568                 :           0 :                         std::regex pattern(m_fileNameExt);
     569                 :           0 :                         return std::regex_match(vPath, pattern);
     570                 :           0 :                     } catch (...) {
     571                 :           0 :                         return false;
     572                 :           0 :                     }
     573                 :           0 :                 }
     574                 :           0 :             }
     575                 :           0 :             return false;
     576                 :           0 :         }
     577                 :             :     };
     578                 :             : 
     579                 :             :     std::mutex m_patternsMutex;
     580                 :             :     std::vector<PatternPtr> m_watchPatterns;
     581                 :             : 
     582                 :             :     class IBackend {
     583                 :             :     public:
     584                 :           2 :         virtual ~IBackend() = default;
     585                 :             :         virtual bool onStart(Watcher &owner) = 0;
     586                 :             :         virtual void onStop(Watcher &owner) = 0;
     587                 :             :         virtual bool addPattern(Watcher &owner, const PatternPtr &pattern) = 0;
     588                 :             :         virtual void poll(Watcher &owner, std::set<PathResult> &out) = 0;
     589                 :             :     };
     590                 :             : 
     591                 :             :     std::unique_ptr<IBackend> m_backend;
     592                 :             : 
     593                 :           4 :     static void m_logPathResult(const PathResult& vPathResult) {
     594                 :             : #ifdef _DEBUG
     595                 :             :         const char *mode = " ";
     596                 :             :         switch (vPathResult.modifType) {
     597                 :             :             case PathResult::ModifType::CREATION: mode = "CREATION"; break;
     598                 :             :             case PathResult::ModifType::DELETION: mode = "DELETION"; break;
     599                 :             :             case PathResult::ModifType::MODIFICATION: mode = "MODIFICATION"; break;
     600                 :             :             case PathResult::ModifType::RENAMED: mode = "RENAMED"; break;
     601                 :             :             default: break;
     602                 :             :         }
     603                 :             :         LogVarLightInfo("Event : RP(%s) OP(%s) NP(%s) MODE(%s)", vPathResult.rootPath.c_str(), vPathResult.oldPath.c_str(), vPathResult.newPath.c_str(), mode);
     604                 :             : #else
     605                 :           4 :         (void)vPathResult;
     606                 :           4 : #endif
     607                 :           4 :     }
     608                 :             : 
     609                 :             :     static void m_emitIfMatch(  //
     610                 :             :         const std::string &rootKey,
     611                 :             :         const std::vector<PatternWeak> &relatedPatterns,
     612                 :             :         const std::string &relNewName,
     613                 :             :         PathResult &pr,
     614                 :           4 :         std::set<PathResult> &out) {
     615                 :           4 :         pr.rootPath = rootKey;
     616         [ +  + ]:           4 :         for (const auto &pw : relatedPatterns) {
     617         [ +  - ]:           4 :             if (auto p = pw.lock()) {
     618         [ +  - ]:           4 :                 if (p->getPhysicalType() == Pattern::PhysicalType::DIR) {
     619                 :           4 :                     out.emplace(pr);
     620                 :           4 :                     m_logPathResult(pr);
     621                 :           4 :                 } else {
     622 [ #  # ][ #  # ]:           0 :                     if (!relNewName.empty() && p->isPatternMatch(relNewName)) {
     623                 :           0 :                         out.emplace(pr);
     624                 :           0 :                         m_logPathResult(pr);
     625                 :           0 :                     }
     626                 :           0 :                 }
     627                 :           4 :             }
     628                 :           4 :         }
     629                 :           4 :     }
     630                 :             : 
     631                 :           2 :     std::string m_getAppPath() { return ez::App().getAppPath(); }
     632                 :           1 :     std::string m_removeAppPath(const std::string &vPath) {
     633                 :           1 :         size_t pos = vPath.find(m_appPath);
     634         [ -  + ]:           1 :         if (pos != std::string::npos) {
     635                 :           0 :             return vPath.substr(pos + m_appPath.size());
     636                 :           0 :         }
     637                 :           1 :         return vPath;
     638                 :           1 :     }
     639                 :             : 
     640                 :             : public:
     641                 :           2 :     Watcher() : m_appPath(m_getAppPath()) {}
     642                 :           2 :     ~Watcher() { stop(); }
     643                 :             : 
     644                 :           2 :     void setCallback(Callback vCallback) { m_callback = vCallback; }
     645                 :             : 
     646                 :             :     // watch a directory. can be absolute or relative the to the app
     647                 :             :     // no widlcards or regex are supported for the directory
     648                 :           2 :     bool watchDirectory(const std::string &vPath) { return m_registerPattern(Pattern::sCreatePath(vPath, Pattern::PatternType::PATH, Pattern::PhysicalType::DIR)); }
     649                 :             : 
     650                 :             :     // will watch from a path a filename.
     651                 :             :     // vRootPath : no widlcards or regex are supported
     652                 :             :     // vFileNameExt : can contain wildcards. regex patterns is authorized but must start with a '(' and end with a ')'
     653                 :           0 :     bool watchFile(const std::string &vRootPath, const std::string &vFileNameExt) {
     654                 :           0 :         if (vRootPath.empty() || vFileNameExt.empty()) {
     655                 :           0 :             return false;
     656                 :           0 :         }
     657                 :           0 :         Pattern::PatternType type = Pattern::PatternType::PATH;
     658                 :           0 :         if (vFileNameExt.front() == '(' && vFileNameExt.back() == ')') {
     659                 :           0 :             type = Pattern::PatternType::REGEX;
     660                 :           0 :         } else if (vFileNameExt.find('*') != std::string::npos) {
     661                 :           0 :             type = Pattern::PatternType::GLOB;
     662                 :           0 :         }
     663                 :           0 :         return m_registerPattern(Pattern::sCreatePathFile(vRootPath, vFileNameExt, type, Pattern::PhysicalType::FILE));
     664                 :           0 :     }
     665                 :             : 
     666                 :           4 :     bool start() {
     667 [ +  + ][ +  + ]:           4 :         if (m_running || m_callback == nullptr) {
     668                 :           2 :             return false;
     669                 :           2 :         }
     670                 :           2 :         m_backend = m_createBackend();
     671         [ -  + ]:           2 :         if (!m_backend) {
     672                 :           0 :             return false;
     673                 :           0 :         }
     674         [ -  + ]:           2 :         if (!m_backend->onStart(*this)) {
     675                 :           0 :             m_backend.reset();
     676                 :           0 :             return false;
     677                 :           0 :         }
     678                 :           2 :         {
     679                 :           2 :             std::lock_guard<std::mutex> lock(m_patternsMutex);
     680         [ -  + ]:           2 :             for (const auto &p : m_watchPatterns) {
     681                 :           0 :                 (void)m_backend->addPattern(*this, p);
     682                 :           0 :             }
     683                 :           2 :         }
     684                 :           2 :         m_running = true;
     685                 :           2 :         m_thread = std::thread(&Watcher::m_threadLoop, this);
     686                 :           2 :         return true;
     687                 :           2 :     }
     688                 :             : 
     689                 :           5 :     bool stop() {
     690         [ +  + ]:           5 :         if (!m_running) {
     691                 :           3 :             return false;
     692                 :           3 :         }
     693                 :           2 :         m_running = false;
     694         [ +  - ]:           2 :         if (m_thread.joinable()) {
     695                 :           2 :             m_thread.join();
     696                 :           2 :         }
     697         [ +  - ]:           2 :         if (m_backend) {
     698                 :           2 :             m_backend->onStop(*this);
     699                 :           2 :             m_backend.reset();
     700                 :           2 :         }
     701                 :           2 :         return true;
     702                 :           5 :     }
     703                 :             : 
     704                 :             : private:
     705                 :           2 :     bool m_registerPattern(const PatternPtr &pat) {
     706         [ +  + ]:           2 :         if (!pat) {
     707                 :           1 :             return false;
     708                 :           1 :         }
     709                 :           1 :         {
     710                 :           1 :             std::lock_guard<std::mutex> lock(m_patternsMutex);
     711                 :           1 :             m_watchPatterns.push_back(pat);
     712                 :           1 :         }
     713         [ +  - ]:           1 :         if (m_backend) {
     714                 :           1 :             return m_backend->addPattern(*this, pat);
     715                 :           1 :         }
     716                 :           0 :         return true;
     717                 :           1 :     }
     718                 :             : 
     719                 :           2 :     void m_threadLoop() {
     720                 :           2 :         std::set<PathResult> files;
     721         [ +  + ]:          17 :         while (m_running) {
     722                 :          15 :             m_backend->poll(*this, files);
     723         [ +  + ]:          15 :             if (!files.empty()) {
     724                 :           4 :                 m_callback(files);
     725                 :           4 :                 files.clear();
     726                 :           4 :             }
     727                 :          15 :             std::this_thread::sleep_for(std::chrono::milliseconds(10));
     728                 :          15 :         }
     729                 :           2 :     }
     730                 :             : 
     731                 :             :     std::unique_ptr<IBackend> m_createBackend();
     732                 :             : 
     733                 :             :     // =============================== Backend Windows ===============================
     734                 :             : #if defined(WINDOWS_OS)
     735                 :             : #ifndef WIN32_LEAN_AND_MEAN
     736                 :             : #define WIN32_LEAN_AND_MEAN
     737                 :             : #endif
     738                 :             : #include <Windows.h>
     739                 :             :     class BackendWindows : public IBackend {
     740                 :             :     public:
     741                 :             :         typedef std::wstring PathType;
     742                 :             :         struct WatchHandle {
     743                 :             :             HANDLE hDir{};
     744                 :             :             PathType path;
     745                 :             :             std::vector<PatternWeak> relatedPatterns;
     746                 :             :             HANDLE hEvent{};
     747                 :             :             OVERLAPPED ov{};
     748                 :             :             std::array<uint8_t, 4096> buffer{};
     749                 :             :             bool inFlight{false};
     750                 :             :         };
     751                 :             : 
     752                 :             :     private:
     753                 :             :         std::mutex m_mtx;
     754                 :             :         std::unordered_map<std::string, WatchHandle> m_watchHandles;
     755                 :             :         std::vector<WatchHandle *> m_handles;
     756                 :             :         std::atomic<bool> m_isDirty{false};
     757                 :             : 
     758                 :             :     public:
     759                 :             : 
     760                 :             :         bool onStart(Watcher &owner) override {
     761                 :             :             (void)owner;
     762                 :             :             m_isDirty = true;
     763                 :             :             return true;
     764                 :             :         }
     765                 :             : 
     766                 :             :         bool addPattern(Watcher &owner, const PatternPtr &pattern) override {
     767                 :             :             if (!pattern) {
     768                 :             :                 return false;
     769                 :             :             }
     770                 :             :             const std::string rootKey = owner.m_removeAppPath(pattern->getPath());
     771                 :             :             std::lock_guard<std::mutex> lock(m_mtx);
     772                 :             :             auto it = m_watchHandles.find(rootKey);
     773                 :             :             if (it != m_watchHandles.end()) {
     774                 :             :                 it->second.relatedPatterns.push_back(pattern);
     775                 :             :             } else {
     776                 :             :                 WatchHandle hnd;
     777                 :             :                 hnd.path = ez::str::utf8Decode(rootKey);
     778                 :             :                 hnd.hDir = CreateFileW(
     779                 :             :                     hnd.path.c_str(),
     780                 :             :                     FILE_LIST_DIRECTORY,
     781                 :             :                     FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
     782                 :             :                     NULL,
     783                 :             :                     OPEN_EXISTING,
     784                 :             :                     FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED,
     785                 :             :                     NULL);
     786                 :             :                 if (hnd.hDir == INVALID_HANDLE_VALUE) {
     787                 :             :                     LogVarError("Err : Unable to open directory : %s", rootKey.c_str());
     788                 :             :                     return false;
     789                 :             :                 }
     790                 :             :                 hnd.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
     791                 :             :                 hnd.ov.hEvent = hnd.hEvent;
     792                 :             :                 ZeroMemory(&hnd.ov, sizeof(hnd.ov));
     793                 :             :                 hnd.inFlight = false;
     794                 :             :                 hnd.relatedPatterns.push_back(pattern);
     795                 :             :                 m_watchHandles[rootKey] = hnd;
     796                 :             :                 auto &ref = m_watchHandles[rootKey];
     797                 :             :                 m_postRead(ref);  // *** Arm immediately to avoid missing the first changes ***
     798                 :             :             }
     799                 :             :             m_isDirty = true;
     800                 :             :             return true;
     801                 :             :         }
     802                 :             : 
     803                 :             :         void onStop(Watcher &owner) override {
     804                 :             :             (void)owner;
     805                 :             :             std::lock_guard<std::mutex> lock(m_mtx);
     806                 :             :             for (auto &kv : m_watchHandles) {
     807                 :             :                 auto &h = kv.second;
     808                 :             :                 if (h.inFlight) {
     809                 :             :                     CancelIoEx(h.hDir, &h.ov);  // safe if no I/O too
     810                 :             :                 }
     811                 :             :                 if (h.hEvent) {
     812                 :             :                     CloseHandle(h.hEvent);
     813                 :             :                     h.hEvent = nullptr;
     814                 :             :                 }
     815                 :             :                 if (h.hDir && h.hDir != INVALID_HANDLE_VALUE) {
     816                 :             :                     CloseHandle(h.hDir);
     817                 :             :                     h.hDir = INVALID_HANDLE_VALUE;
     818                 :             :                 }
     819                 :             :             }
     820                 :             :             m_watchHandles.clear();
     821                 :             :             m_handles.clear();
     822                 :             :         }
     823                 :             : 
     824                 :             :         // ---- Your exact functions (content preserved, only owner passed when needed) ----
     825                 :             :         void completePathResult(PathResult &voResult, const FILE_NOTIFY_INFORMATION *vpNotify, Watcher &owner) {
     826                 :             :             const auto chFile = owner.m_removeAppPath(ez::str::utf8Encode(std::wstring(vpNotify->FileName, vpNotify->FileNameLength / sizeof(WCHAR))));
     827                 :             :             const char *mode = " ";
     828                 :             :             switch (vpNotify->Action) {
     829                 :             :                 case FILE_ACTION_ADDED: {
     830                 :             :                     voResult.modifType = PathResult::ModifType::CREATION;
     831                 :             :                     voResult.newPath = chFile;
     832                 :             :                 } break;
     833                 :             :                 case FILE_ACTION_REMOVED: {
     834                 :             :                     voResult.modifType = PathResult::ModifType::DELETION;
     835                 :             :                     voResult.newPath = chFile;
     836                 :             :                 } break;
     837                 :             :                 case FILE_ACTION_MODIFIED: {
     838                 :             :                     voResult.modifType = PathResult::ModifType::MODIFICATION;
     839                 :             :                     voResult.newPath = chFile;
     840                 :             :                 } break;
     841                 :             :                 case FILE_ACTION_RENAMED_OLD_NAME: {
     842                 :             :                     voResult.modifType = PathResult::ModifType::RENAMED;
     843                 :             :                     voResult.oldPath = chFile;
     844                 :             :                 } break;
     845                 :             :                 case FILE_ACTION_RENAMED_NEW_NAME: {
     846                 :             :                     voResult.modifType = PathResult::ModifType::RENAMED;
     847                 :             :                     voResult.newPath = chFile;
     848                 :             :                 } break;
     849                 :             :             }
     850                 :             :         }
     851                 :             : 
     852                 :             :         bool m_postRead(WatchHandle &hnd) {
     853                 :             :             if (hnd.inFlight) {
     854                 :             :                 return true;
     855                 :             :             }
     856                 :             : 
     857                 :             :             // Reset event & OVERLAPPED before issuing a new read
     858                 :             :             ResetEvent(hnd.hEvent);
     859                 :             :             ZeroMemory(&hnd.ov, sizeof(hnd.ov));
     860                 :             :             hnd.ov.hEvent = hnd.hEvent;
     861                 :             : 
     862                 :             :             const DWORD kMask = FILE_NOTIFY_CHANGE_FILE_NAME |  // file create/delete/rename in root dir
     863                 :             :                 FILE_NOTIFY_CHANGE_LAST_WRITE;                  // file content modifications
     864                 :             : 
     865                 :             :             // In OVERLAPPED mode, lpBytesReturned MUST be nullptr
     866                 :             :             BOOL ok = ReadDirectoryChangesW(
     867                 :             :                 hnd.hDir,
     868                 :             :                 hnd.buffer.data(),
     869                 :             :                 static_cast<DWORD>(hnd.buffer.size()),
     870                 :             :                 FALSE,  // do NOT watch subtree
     871                 :             :                 kMask,
     872                 :             :                 nullptr,  // must be null for OVERLAPPED
     873                 :             :                 &hnd.ov,
     874                 :             :                 nullptr);
     875                 :             : 
     876                 :             :             if (!ok) {
     877                 :             :                 DWORD err = GetLastError();
     878                 :             :                 if (err != ERROR_IO_PENDING) {
     879                 :             :                     LogVarWarning("ReadDirectoryChangesW(OVERLAPPED) failed: %u", err);
     880                 :             :                     return false;
     881                 :             :                 }
     882                 :             :             }
     883                 :             : 
     884                 :             :             hnd.inFlight = true;
     885                 :             :             return true;
     886                 :             :         }
     887                 :             : 
     888                 :             :         // In class Watcher::BackendWindows (private)
     889                 :             :         void m_pollOneHandle(WatchHandle *vpHandle, std::set<PathResult> &voFiles, Watcher &owner) {
     890                 :             :             // Non-blocking poll of a single handle; parse results and re-arm the async read.
     891                 :             :             if (vpHandle == nullptr) {
     892                 :             :                 return;
     893                 :             :             }
     894                 :             : 
     895                 :             :             // 2) Poll event (non-blocking)
     896                 :             :             DWORD wr = WaitForSingleObject(vpHandle->hEvent, 0);
     897                 :             : #ifdef _DEBUG
     898                 :             :             switch (wr) {
     899                 :             :                 case WAIT_ABANDONED: {
     900                 :             :                     LogVarLightInfo("WAIT_ABANDONED");
     901                 :             :                 } break;
     902                 :             :                 case WAIT_OBJECT_0: {
     903                 :             :                     // LogVarLightInfo("WAIT_OBJECT_0");
     904                 :             :                 } break;
     905                 :             :                 case WAIT_TIMEOUT: {
     906                 :             :                     //   LogVarLightInfo("WAIT_TIMEOUT");
     907                 :             :                 } break;
     908                 :             :                 case WAIT_FAILED: {
     909                 :             :                     LogVarLightInfo("WAIT_FAILED");
     910                 :             :                 } break;
     911                 :             :             }
     912                 :             : #endif
     913                 :             :             if (wr != WAIT_OBJECT_0) {
     914                 :             :                 // Nothing ready for this handle
     915                 :             :                 return;
     916                 :             :             }
     917                 :             : 
     918                 :             :             // I/O finished
     919                 :             :             DWORD bytes = 0;
     920                 :             :             if (GetOverlappedResult(vpHandle->hDir, &vpHandle->ov, &bytes, FALSE)) {
     921                 :             :                 // Parse buffer [0..bytes)
     922                 :             :                 DWORD offset = 0;
     923                 :             :                 PathResult pr{};
     924                 :             :                 pr.clear();
     925                 :             :                 pr.rootPath = ez::str::utf8Encode(vpHandle->path);
     926                 :             : 
     927                 :             :                 while (offset < bytes) {
     928                 :             :                     const auto *pNotify = reinterpret_cast<const FILE_NOTIFY_INFORMATION *>(vpHandle->buffer.data() + offset);
     929                 :             :                     completePathResult(pr, pNotify, owner);
     930                 :             :                     // Only emit when we have a valid newPath (pairs OLD/NEW may come split)
     931                 :             :                     if (!pr.newPath.empty()) {
     932                 :             :                         // snapshot patterns under lock to avoid concurrent modification
     933                 :             :                         std::vector<PatternWeak> relatedCopy;
     934                 :             :                         {
     935                 :             :                             std::lock_guard<std::mutex> lock(m_mtx);
     936                 :             :                             relatedCopy = vpHandle->relatedPatterns;
     937                 :             :                         }
     938                 :             :                         for (const auto &patW : relatedCopy) {
     939                 :             :                             if (auto pPat = patW.lock()) {
     940                 :             :                                 if (pPat->getPhysicalType() == Pattern::PhysicalType::DIR) {
     941                 :             :                                     voFiles.emplace(pr);
     942                 :             :                                     m_logPathResult(pr);
     943                 :             :                                 } else {
     944                 :             :                                     if (pPat->isPatternMatch(pr.newPath)) {
     945                 :             :                                         voFiles.emplace(pr);
     946                 :             :                                         m_logPathResult(pr);
     947                 :             :                                     }
     948                 :             :                                 }
     949                 :             :                             }
     950                 :             :                         }
     951                 :             :                     }
     952                 :             :                     if (pNotify->NextEntryOffset == 0) {
     953                 :             :                         break;
     954                 :             :                     }
     955                 :             :                     offset += pNotify->NextEntryOffset;
     956                 :             :                 }
     957                 :             :             } else {
     958                 :             :                 LogVarWarning("GetOverlappedResult failed: %u", GetLastError());
     959                 :             :             }
     960                 :             : 
     961                 :             :             // Mark as not in-flight and immediately re-arm
     962                 :             :             vpHandle->inFlight = false;
     963                 :             :             (void)m_postRead(*vpHandle);
     964                 :             :         }
     965                 :             : 
     966                 :             :         void poll(Watcher &owner, std::set<PathResult> &out) override {
     967                 :             :             if (m_isDirty.exchange(false)) {
     968                 :             :                 std::lock_guard<std::mutex> lock(m_mtx);
     969                 :             :                 m_handles.clear();
     970                 :             :                 m_handles.reserve(m_watchHandles.size());
     971                 :             :                 for (auto &kv : m_watchHandles) {
     972                 :             :                     m_handles.push_back(&kv.second);  // store pointer
     973                 :             :                 }
     974                 :             :             }
     975                 :             :             for (auto *hnd : m_handles) {
     976                 :             :                 if (!hnd->inFlight) {
     977                 :             :                     m_postRead(*hnd);
     978                 :             :                 }
     979                 :             :                 m_pollOneHandle(hnd, out, owner);
     980                 :             :             }
     981                 :             :         }
     982                 :             :     };
     983                 :             : #endif  // WINDOWS_OS
     984                 :             : 
     985                 :             :     // =============================== Backend Linux ===============================
     986                 :             : #if defined(LINUX_OS)
     987                 :             : #include <sys/inotify.h>
     988                 :             : #include <unistd.h>
     989                 :             : #include <limits.h>
     990                 :             : #include <errno.h>
     991                 :             : 
     992                 :             :     class BackendLinux : public IBackend {
     993                 :             :     public:
     994                 :             :         struct WatchHandle {
     995                 :             :             int wd{-1};
     996                 :             :             std::string rootKey;
     997                 :             :             std::vector<PatternWeak> relatedPatterns;
     998                 :             :         };
     999                 :             : 
    1000                 :           2 :         bool onStart(Watcher &owner) override {
    1001                 :           2 :             (void)owner;
    1002                 :           2 :             m_fd = inotify_init1(IN_NONBLOCK | IN_CLOEXEC);
    1003         [ -  + ]:           2 :             if (m_fd < 0) {
    1004                 :           0 :                 LogVarError("Error: Unable to initialize inotify.");
    1005                 :           0 :                 return false;
    1006                 :           0 :             }
    1007                 :           2 :             return true;
    1008                 :           2 :         }
    1009                 :             : 
    1010                 :           2 :         void onStop(Watcher &owner) override {
    1011                 :           2 :             (void)owner;
    1012         [ +  + ]:           2 :             for (auto &kv : m_handles) {
    1013         [ +  - ]:           1 :                 if (kv.second.wd >= 0) {
    1014                 :           1 :                     inotify_rm_watch(m_fd, kv.second.wd);
    1015                 :           1 :                     kv.second.wd = -1;
    1016                 :           1 :                 }
    1017                 :           1 :             }
    1018         [ +  - ]:           2 :             if (m_fd >= 0) {
    1019                 :           2 :                 close(m_fd);
    1020                 :           2 :                 m_fd = -1;
    1021                 :           2 :             }
    1022                 :           2 :             m_wdToRoot.clear();
    1023                 :           2 :             m_pendingRename.clear();
    1024                 :           2 :             m_handles.clear();
    1025                 :           2 :             m_buf.clear();
    1026                 :           2 :         }
    1027                 :             : 
    1028                 :           1 :         bool addPattern(Watcher &owner, const PatternPtr &pattern) override {
    1029         [ -  + ]:           1 :             if (!pattern) {
    1030                 :           0 :                 return false;
    1031                 :           0 :             }
    1032                 :           1 :             const std::string rootKey = owner.m_removeAppPath(pattern->getPath());
    1033                 :           1 :             auto it = m_handles.find(rootKey);
    1034         [ -  + ]:           1 :             if (it != m_handles.end()) {
    1035                 :           0 :                 it->second.relatedPatterns.push_back(pattern);
    1036                 :           1 :             } else {
    1037                 :           1 :                 const uint32_t mask = IN_CREATE | IN_DELETE | IN_MODIFY | IN_MOVED_FROM | IN_MOVED_TO;
    1038                 :           1 :                 int wd = inotify_add_watch(m_fd, rootKey.c_str(), mask);
    1039         [ -  + ]:           1 :                 if (wd < 0) {
    1040                 :           0 :                     LogVarError("Error: inotify_add_watch failed on %s (errno %d)", rootKey.c_str(), errno);
    1041                 :           0 :                     return false;
    1042                 :           0 :                 }
    1043                 :           1 :                 WatchHandle hnd;
    1044                 :           1 :                 hnd.wd = wd;
    1045                 :           1 :                 hnd.rootKey = rootKey;
    1046                 :           1 :                 hnd.relatedPatterns.push_back(pattern);
    1047                 :           1 :                 m_handles[rootKey] = hnd;
    1048                 :           1 :                 m_wdToRoot[wd] = rootKey;
    1049                 :           1 :             }
    1050                 :           1 :             return true;
    1051                 :           1 :         }
    1052                 :             : 
    1053                 :          15 :         void poll(Watcher &owner, std::set<PathResult> &out) override {
    1054                 :          15 :             (void)owner;
    1055         [ -  + ]:          15 :             if (m_fd < 0) {
    1056                 :           0 :                 return;
    1057                 :           0 :             }
    1058         [ +  + ]:          15 :             if (m_buf.size() < 64 * 1024) {
    1059                 :           1 :                 m_buf.resize(64 * 1024);
    1060                 :           1 :             }
    1061                 :             : 
    1062                 :          15 :             ssize_t n = read(m_fd, m_buf.data(), m_buf.size());
    1063         [ +  + ]:          15 :             if (n <= 0) {
    1064                 :          11 :                 return;
    1065                 :          11 :             }
    1066                 :             : 
    1067                 :           4 :             ssize_t off = 0;
    1068         [ +  + ]:           9 :             while (off < n) {
    1069                 :           5 :                 const inotify_event *ev = reinterpret_cast<const inotify_event *>(m_buf.data() + off);
    1070                 :             : 
    1071                 :           5 :                 auto itRoot = m_wdToRoot.find(ev->wd);
    1072         [ +  - ]:           5 :                 if (itRoot != m_wdToRoot.end()) {
    1073                 :           5 :                     const std::string &rootKey = itRoot->second;
    1074                 :           5 :                     auto itH = m_handles.find(rootKey);
    1075         [ +  - ]:           5 :                     if (itH != m_handles.end()) {
    1076                 :           5 :                         const bool isDir = (ev->mask & IN_ISDIR) != 0;
    1077 [ +  - ][ +  - ]:           5 :                         if (!isDir && ev->len > 0) {
    1078                 :           5 :                             const std::string rel(ev->name);
    1079                 :           5 :                             PathResult pr{};
    1080         [ +  + ]:           5 :                             if (ev->mask & IN_CREATE) {
    1081                 :           1 :                                 pr.modifType = PathResult::ModifType::CREATION;
    1082                 :           1 :                                 pr.newPath = rel;
    1083                 :           1 :                                 m_emitIfMatch(rootKey, itH->second.relatedPatterns, rel, pr, out);
    1084         [ +  + ]:           4 :                             } else if (ev->mask & IN_DELETE) {
    1085                 :           1 :                                 pr.modifType = PathResult::ModifType::DELETION;
    1086                 :           1 :                                 pr.newPath = rel;
    1087                 :           1 :                                 m_emitIfMatch(rootKey, itH->second.relatedPatterns, rel, pr, out);
    1088         [ +  + ]:           3 :                             } else if (ev->mask & IN_MODIFY) {
    1089                 :           1 :                                 pr.modifType = PathResult::ModifType::MODIFICATION;
    1090                 :           1 :                                 pr.newPath = rel;
    1091                 :           1 :                                 m_emitIfMatch(rootKey, itH->second.relatedPatterns, rel, pr, out);
    1092         [ +  + ]:           2 :                             } else if (ev->mask & IN_MOVED_FROM) {
    1093                 :           1 :                                 m_pendingRename[ev->cookie] = std::make_pair(ev->wd, rel);
    1094         [ +  - ]:           1 :                             } else if (ev->mask & IN_MOVED_TO) {
    1095                 :           1 :                                 auto itCookie = m_pendingRename.find(ev->cookie);
    1096         [ +  - ]:           1 :                                 if (itCookie != m_pendingRename.end()) {
    1097         [ +  - ]:           1 :                                     if (itCookie->second.first == ev->wd) {
    1098                 :           1 :                                         pr.modifType = PathResult::ModifType::RENAMED;
    1099                 :           1 :                                         pr.oldPath = itCookie->second.second;
    1100                 :           1 :                                         pr.newPath = rel;
    1101                 :           1 :                                         m_emitIfMatch(rootKey, itH->second.relatedPatterns, rel, pr, out);
    1102                 :           1 :                                     } else {
    1103                 :           0 :                                         pr.modifType = PathResult::ModifType::CREATION;
    1104                 :           0 :                                         pr.newPath = rel;
    1105                 :           0 :                                         m_emitIfMatch(rootKey, itH->second.relatedPatterns, rel, pr, out);
    1106                 :           0 :                                     }
    1107                 :           1 :                                     m_pendingRename.erase(itCookie);
    1108                 :           1 :                                 } else {
    1109                 :           0 :                                     pr.modifType = PathResult::ModifType::CREATION;
    1110                 :           0 :                                     pr.newPath = rel;
    1111                 :           0 :                                     m_emitIfMatch(rootKey, itH->second.relatedPatterns, rel, pr, out);
    1112                 :           0 :                                 }
    1113                 :           1 :                             }
    1114                 :           5 :                         }
    1115                 :           5 :                     }
    1116                 :           5 :                 }
    1117                 :             : 
    1118                 :           5 :                 off += sizeof(inotify_event) + ev->len;
    1119                 :           5 :             }
    1120                 :           4 :         }
    1121                 :             : 
    1122                 :             :     private:
    1123                 :             :         int m_fd{-1};
    1124                 :             :         std::unordered_map<std::string, WatchHandle> m_handles;
    1125                 :             :         std::unordered_map<int, std::string> m_wdToRoot;
    1126                 :             :         std::unordered_map<uint32_t, std::pair<int, std::string>> m_pendingRename;
    1127                 :             :         std::vector<uint8_t> m_buf;
    1128                 :             :     };
    1129                 :             : #endif  // LINUX_OS
    1130                 :             : 
    1131                 :             :     // =============================== Backend macOS ===============================
    1132                 :             : #if defined(APPLE_OS)
    1133                 :             :     class BackendMac : public IBackend {
    1134                 :             :     public:
    1135                 :             : #include <CoreServices/CoreServices.h>
    1136                 :             : 
    1137                 :             :         struct WatchHandle {
    1138                 :             :             std::string rootKey;
    1139                 :             :             std::vector<PatternWeak> relatedPatterns;
    1140                 :             :         };
    1141                 :             : 
    1142                 :             :         bool onStart(Watcher &owner) override {
    1143                 :             :             (void)owner;
    1144                 :             :             m_dirty = true;
    1145                 :             :             return true;
    1146                 :             :         }
    1147                 :             : 
    1148                 :             :         void onStop(Watcher &owner) override {
    1149                 :             :             (void)owner;
    1150                 :             :             if (m_stream) {
    1151                 :             :                 FSEventStreamStop(m_stream);
    1152                 :             :                 FSEventStreamInvalidate(m_stream);
    1153                 :             :                 FSEventStreamRelease(m_stream);
    1154                 :             :                 m_stream = nullptr;
    1155                 :             :             }
    1156                 :             :             std::lock_guard<std::mutex> lock(m_qmtx);
    1157                 :             :             m_queue.clear();
    1158                 :             :             m_handles.clear();
    1159                 :             :         }
    1160                 :             : 
    1161                 :             :         bool addPattern(Watcher &owner, const PatternPtr &pattern) override {
    1162                 :             :             if (!pattern) {
    1163                 :             :                 return false;
    1164                 :             :             }
    1165                 :             :             const std::string rootKey = owner.m_removeAppPath(pattern->getPath());
    1166                 :             :             auto it = m_handles.find(rootKey);
    1167                 :             :             if (it == m_handles.end()) {
    1168                 :             :                 WatchHandle h;
    1169                 :             :                 h.rootKey = rootKey;
    1170                 :             :                 h.relatedPatterns.push_back(pattern);
    1171                 :             :                 m_handles[rootKey] = std::move(h);
    1172                 :             :                 m_dirty = true;
    1173                 :             :             } else {
    1174                 :             :                 it->second.relatedPatterns.push_back(pattern);
    1175                 :             :             }
    1176                 :             :             return true;
    1177                 :             :         }
    1178                 :             : 
    1179                 :             :         void poll(Watcher &owner, std::set<PathResult> &out) override {
    1180                 :             :             (void)owner;
    1181                 :             : 
    1182                 :             :             if (m_dirty) {
    1183                 :             :                 if (m_stream) {
    1184                 :             :                     FSEventStreamStop(m_stream);
    1185                 :             :                     FSEventStreamInvalidate(m_stream);
    1186                 :             :                     FSEventStreamRelease(m_stream);
    1187                 :             :                     m_stream = nullptr;
    1188                 :             :                 }
    1189                 :             : 
    1190                 :             :                 CFMutableArrayRef paths = CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks);
    1191                 :             :                 for (const auto &kv : m_handles) {
    1192                 :             :                     if (!kv.first.empty()) {
    1193                 :             :                         CFStringRef s = CFStringCreateWithCString(NULL, kv.first.c_str(), kCFStringEncodingUTF8);
    1194                 :             :                         CFArrayAddValue(paths, s);
    1195                 :             :                         CFRelease(s);
    1196                 :             :                     }
    1197                 :             :                 }
    1198                 :             : 
    1199                 :             :                 if (CFArrayGetCount(paths) > 0) {
    1200                 :             :                     FSEventStreamContext ctx{};
    1201                 :             :                     ctx.info = this;
    1202                 :             :                     const FSEventStreamCreateFlags flags = kFSEventStreamCreateFlagFileEvents | kFSEventStreamCreateFlagNoDefer;
    1203                 :             : 
    1204                 :             :                     m_stream = FSEventStreamCreate(NULL, &BackendMac::sCallback, &ctx, paths, kFSEventStreamEventIdSinceNow, 0.1, flags);
    1205                 :             :                     if (m_stream) {
    1206                 :             :                         FSEventStreamScheduleWithRunLoop(m_stream, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);
    1207                 :             :                         FSEventStreamStart(m_stream);
    1208                 :             :                     }
    1209                 :             :                 }
    1210                 :             :                 CFRelease(paths);
    1211                 :             :                 m_dirty = false;
    1212                 :             :             }
    1213                 :             : 
    1214                 :             :             if (m_stream) {
    1215                 :             :                 CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.0, true);
    1216                 :             :             }
    1217                 :             : 
    1218                 :             :             {
    1219                 :             :                 std::lock_guard<std::mutex> lock(m_qmtx);
    1220                 :             :                 for (const PathResult &pr : m_queue) {
    1221                 :             :                     out.emplace(pr);
    1222                 :             :                 }
    1223                 :             :                 m_queue.clear();
    1224                 :             :             }
    1225                 :             :         }
    1226                 :             : 
    1227                 :             :     private:
    1228                 :             :         static void
    1229                 :             :         sCallback(ConstFSEventStreamRef, void *userData, size_t numEvents, void *eventPaths, const FSEventStreamEventFlags *eventFlags, const FSEventStreamEventId *) {
    1230                 :             :             BackendMac *self = static_cast<BackendMac *>(userData);
    1231                 :             :             if (!self) {
    1232                 :             :                 return;
    1233                 :             :             }
    1234                 :             : 
    1235                 :             :             char **paths = static_cast<char **>(eventPaths);
    1236                 :             : 
    1237                 :             :             // Snapshot roots for matching and pattern list
    1238                 :             :             std::vector<std::pair<std::string, std::vector<PatternWeak>>> roots;
    1239                 :             :             roots.reserve(self->m_handles.size());
    1240                 :             :             for (const auto &kv : self->m_handles) {
    1241                 :             :                 roots.emplace_back(kv.first, kv.second.relatedPatterns);
    1242                 :             :             }
    1243                 :             : 
    1244                 :             :             std::vector<PathResult> local;
    1245                 :             :             local.reserve(numEvents);
    1246                 :             : 
    1247                 :             :             for (size_t i = 0; i < numEvents; ++i) {
    1248                 :             :                 const std::string changed = paths[i];
    1249                 :             :                 const auto flags = eventFlags[i];
    1250                 :             : 
    1251                 :             :                 // Only files
    1252                 :             :                 if ((flags & kFSEventStreamEventFlagItemIsFile) == 0) {
    1253                 :             :                     continue;
    1254                 :             :                 }
    1255                 :             : 
    1256                 :             :                 // Find root; produce relative path
    1257                 :             :                 for (const auto &r : roots) {
    1258                 :             :                     const std::string &rootKey = r.first;
    1259                 :             :                     if (changed.size() <= rootKey.size()) {
    1260                 :             :                         continue;
    1261                 :             :                     }
    1262                 :             :                     if (changed.compare(0, rootKey.size(), rootKey) != 0) {
    1263                 :             :                         continue;
    1264                 :             :                     }
    1265                 :             :                     const char sep = changed[rootKey.size()];
    1266                 :             :                     if (sep != '/' && sep != '\\') {
    1267                 :             :                         continue;
    1268                 :             :                     }
    1269                 :             : 
    1270                 :             :                     const std::string rel = changed.substr(rootKey.size() + 1);
    1271                 :             : 
    1272                 :             :                     PathResult pr{};
    1273                 :             :                     pr.rootPath = rootKey;
    1274                 :             :                     if (flags & kFSEventStreamEventFlagItemCreated) {
    1275                 :             :                         pr.modifType = PathResult::ModifType::CREATION;
    1276                 :             :                         pr.newPath = rel;
    1277                 :             :                     } else if (flags & kFSEventStreamEventFlagItemRemoved) {
    1278                 :             :                         pr.modifType = PathResult::ModifType::DELETION;
    1279                 :             :                         pr.newPath = rel;
    1280                 :             :                     } else if (flags & kFSEventStreamEventFlagItemRenamed) {
    1281                 :             :                         pr.modifType = PathResult::ModifType::RENAMED;
    1282                 :             :                         pr.newPath = rel;  // oldPath not available with FSEvents
    1283                 :             :                     } else if (
    1284                 :             :                         flags &
    1285                 :             :                         (kFSEventStreamEventFlagItemModified | kFSEventStreamEventFlagItemInodeMetaMod | kFSEventStreamEventFlagItemFinderInfoMod |
    1286                 :             :                          kFSEventStreamEventFlagItemChangeOwner | kFSEventStreamEventFlagItemXattrMod)) {
    1287                 :             :                         pr.modifType = PathResult::ModifType::MODIFICATION;
    1288                 :             :                         pr.newPath = rel;
    1289                 :             :                     } else {
    1290                 :             :                         continue;
    1291                 :             :                     }
    1292                 :             : 
    1293                 :             :                     // Pattern filtering (DIR accepts all; FILE filters on newPath)
    1294                 :             :                     for (const auto &pw : r.second) {
    1295                 :             :                         if (auto p = pw.lock()) {
    1296                 :             :                             if (p->getPhysicalType() == Pattern::PhysicalType::DIR) {
    1297                 :             :                                 local.push_back(pr);
    1298                 :             :                             } else if (!pr.newPath.empty() && p->isPatternMatch(pr.newPath, false)) {
    1299                 :             :                                 local.push_back(pr);
    1300                 :             :                             }
    1301                 :             :                         }
    1302                 :             :                     }
    1303                 :             :                 }
    1304                 :             :             }
    1305                 :             : 
    1306                 :             :             if (!local.empty()) {
    1307                 :             :                 std::lock_guard<std::mutex> lock(self->m_qmtx);
    1308                 :             :                 for (const auto &pr : local) {
    1309                 :             :                     self->m_queue.push_back(pr);
    1310                 :             :                 }
    1311                 :             :             }
    1312                 :             :         }
    1313                 :             : 
    1314                 :             :         FSEventStreamRef m_stream{nullptr};
    1315                 :             :         bool m_dirty{false};
    1316                 :             :         std::unordered_map<std::string, WatchHandle> m_handles;
    1317                 :             : 
    1318                 :             :         std::mutex m_qmtx;
    1319                 :             :         std::vector<PathResult> m_queue;
    1320                 :             :     };
    1321                 :             : #endif  // APPLE_OS
    1322                 :             : 
    1323                 :             : };  // class Watcher
    1324                 :             : 
    1325                 :             : // =============================== Backend factory ===============================
    1326                 :           2 : inline std::unique_ptr<Watcher::IBackend> Watcher::m_createBackend() {
    1327                 :             : #if defined(WINDOWS_OS)
    1328                 :             :     return std::unique_ptr<IBackend>(new BackendWindows());
    1329                 :             : #elif defined(LINUX_OS)
    1330                 :             :     return std::unique_ptr<IBackend>(new BackendLinux());
    1331                 :             : #elif defined(APPLE_OS)
    1332                 :             :     return std::unique_ptr<IBackend>(new BackendMac());
    1333                 :             : #else
    1334                 :             :     return nullptr;
    1335                 :             : #endif
    1336                 :           2 : }
    1337                 :             : 
    1338                 :             : 
    1339                 :             : }  // namespace file
    1340                 :             : }  // namespace ez
        

Generated by: LCOV version 2.0-1