filesystem.cpp (4524B)
1 /* ReaPack: Package manager for REAPER 2 * Copyright (C) 2015-2025 Christian Fillion 3 * 4 * This program is free software: you can redistribute it and/or modify 5 * it under the terms of the GNU Lesser General Public License as published by 6 * the Free Software Foundation, either version 3 of the License, or 7 * (at your option) any later version. 8 * 9 * This program is distributed in the hope that it will be useful, 10 * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 * GNU Lesser General Public License for more details. 13 * 14 * You should have received a copy of the GNU Lesser General Public License 15 * along with this program. If not, see <http://www.gnu.org/licenses/>. 16 */ 17 18 #include "filesystem.hpp" 19 20 #include "path.hpp" 21 #include "win32.hpp" 22 23 #include <cerrno> 24 #include <cstdio> 25 #include <fstream> 26 #include <sys/stat.h> 27 28 #ifdef _WIN32 29 # include <windows.h> 30 # define stat _stat 31 #endif 32 33 static auto nativePath(const Path &path) 34 { 35 return Win32::widen(path.prependRoot().join()); 36 } 37 38 static bool stat(const Path &path, struct stat *out) 39 { 40 #ifdef _WIN32 41 constexpr auto func = &_wstat; 42 #else 43 constexpr int(*func)(const char *, struct stat *) = &::stat; 44 #endif 45 46 return !func(nativePath(path).c_str(), out); 47 } 48 49 FILE *FS::open(const Path &path) 50 { 51 const auto &fullPath = nativePath(path); 52 53 #ifdef _WIN32 54 FILE *file = nullptr; 55 _wfopen_s(&file, fullPath.c_str(), L"rb"); 56 return file; 57 #else 58 return fopen(fullPath.c_str(), "rb"); 59 #endif 60 } 61 62 bool FS::open(std::ifstream &stream, const Path &path) 63 { 64 stream.open(nativePath(path), std::ios_base::binary); 65 return stream.good(); 66 } 67 68 bool FS::open(std::ofstream &stream, const Path &path) 69 { 70 if(!mkdir(path.dirname())) 71 return false; 72 73 stream.open(nativePath(path), std::ios_base::binary); 74 return stream.good(); 75 } 76 77 bool FS::write(const Path &path, const std::string &contents) 78 { 79 std::ofstream file; 80 if(!open(file, path)) 81 return false; 82 83 file << contents; 84 file.close(); 85 86 return true; 87 } 88 89 bool FS::rename(const TempPath &path) 90 { 91 #ifdef _WIN32 92 remove(path.target()); 93 #endif 94 95 return rename(path.temp(), path.target()); 96 } 97 98 bool FS::rename(const Path &from, const Path &to) 99 { 100 #ifdef _WIN32 101 const auto func = &_wrename; 102 #else 103 const auto func = &::rename; 104 #endif 105 106 return !func(nativePath(from).c_str(), nativePath(to).c_str()); 107 } 108 109 bool FS::remove(const Path &path) 110 { 111 const auto &fullPath = nativePath(path); 112 113 #ifdef _WIN32 114 if(GetFileAttributes(fullPath.c_str()) == INVALID_FILE_ATTRIBUTES 115 && GetLastError() == ERROR_FILE_NOT_FOUND) 116 return true; 117 118 if(GetFileAttributes(fullPath.c_str()) & FILE_ATTRIBUTE_DIRECTORY) 119 return RemoveDirectory(fullPath.c_str()) != 0; 120 else if(!_wremove(fullPath.c_str())) 121 return true; 122 123 // So the file cannot be removed (probably because it's in use)? 124 // Then let's move it somewhere else. And delete it on next startup. 125 // Windows is so great! 126 127 Path workaroundPath = Path::DATA; 128 workaroundPath.append("old_" + path.basename() + ".tmp"); 129 130 return rename(path, workaroundPath); 131 #else 132 return !::remove(fullPath.c_str()); 133 #endif 134 } 135 136 bool FS::removeRecursive(const Path &file) 137 { 138 if(!remove(file)) 139 return false; 140 141 Path dir = file; 142 143 // remove empty directories, but not top-level ones that were created by REAPER 144 while(dir.size() > 2) { 145 dir.removeLast(); 146 147 if(!remove(dir)) 148 break; 149 } 150 151 return true; 152 } 153 154 bool FS::mtime(const Path &path, time_t *time) 155 { 156 struct stat st; 157 158 if(!stat(path, &st)) 159 return false; 160 161 *time = st.st_mtime; 162 163 return true; 164 } 165 166 bool FS::exists(const Path &path, const bool dir) 167 { 168 struct stat st; 169 170 if(stat(path, &st)) 171 return (bool)(st.st_mode & S_IFDIR) == dir; 172 else 173 return false; 174 } 175 176 bool FS::mkdir(const Path &path) 177 { 178 if(exists(path, true)) 179 return true; 180 181 Path fullPath = Path::root(); 182 183 for(const std::string &dir : path) { 184 fullPath.append(dir); 185 186 const auto &joined = Win32::widen(fullPath.join()); 187 188 #ifdef _WIN32 189 if(!CreateDirectory(joined.c_str(), nullptr) && GetLastError() != ERROR_ALREADY_EXISTS) 190 return false; 191 #else 192 if(::mkdir(joined.c_str(), 0777) && errno != EEXIST) 193 return false; 194 #endif 195 } 196 197 return true; 198 } 199 200 Path FS::canonical(const Path &path) 201 { 202 #ifdef _WIN32 203 return path; 204 #else 205 char *resolved = realpath(path.join().c_str(), nullptr); 206 if(!resolved) 207 return path; 208 Path out(resolved); 209 free(resolved); 210 return out; 211 #endif 212 } 213 214 const char *FS::lastError() 215 { 216 return strerror(errno); 217 }