transaction.cpp (8029B)
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 "transaction.hpp" 19 20 #include "config.hpp" 21 #include "download.hpp" 22 #include "errors.hpp" 23 #include "filesystem.hpp" 24 #include "index.hpp" 25 #include "reapack.hpp" 26 #include "remote.hpp" 27 #include "task.hpp" 28 29 #include <cassert> 30 31 #include <reaper_plugin_functions.h> 32 33 Transaction::Transaction() 34 : m_isCancelled(false), m_registry(Path::REGISTRY.prependRoot()) 35 { 36 m_threadPool.onPush >> [this] (ThreadTask *task) { 37 task->onFinishAsync >> [=] { 38 if(task->state() == ThreadTask::Failure) 39 m_receipt.addError(task->error()); 40 }; 41 }; 42 43 m_threadPool.onAbort >> [this] { 44 m_isCancelled = true; 45 std::queue<HostTicket>().swap(m_regQueue); 46 }; 47 48 // run the next task queue when the current one is done 49 m_threadPool.onDone >> std::bind(&Transaction::runTasks, this); 50 } 51 52 void Transaction::synchronize(const Remote &remote, 53 const std::optional<bool> &forceAutoInstall) 54 { 55 if(m_syncedRemotes.count(remote.name())) 56 return; 57 58 m_syncedRemotes.insert(remote.name()); 59 60 InstallOpts opts = g_reapack->config()->install; 61 opts.autoInstall = remote.autoInstall(forceAutoInstall.value_or(opts.autoInstall)); 62 63 m_nextQueue.push(std::make_shared<SynchronizeTask>(remote, true, true, opts, this)); 64 } 65 66 void Transaction::fetchIndexes(const std::vector<Remote> &remotes, const bool stale) 67 { 68 for(const Remote &remote : remotes) 69 m_nextQueue.push(std::make_shared<SynchronizeTask>(remote, stale, false, InstallOpts{}, this)); 70 } 71 72 std::vector<IndexPtr> Transaction::getIndexes(const std::vector<Remote> &remotes) const 73 { 74 std::vector<IndexPtr> indexes; 75 76 for(const Remote &remote : remotes) { 77 const auto &it = m_indexes.find(remote.name()); 78 if(it != m_indexes.end()) 79 indexes.push_back(it->second); 80 } 81 82 return indexes; 83 } 84 85 IndexPtr Transaction::loadIndex(const Remote &remote) 86 { 87 const auto &it = m_indexes.find(remote.name()); 88 if(it != m_indexes.end()) 89 return it->second; 90 91 try { 92 const IndexPtr &ri = Index::load(remote.name()); 93 m_indexes[remote.name()] = ri; 94 return ri; 95 } 96 catch(const reapack_error &e) { 97 m_receipt.addError({ 98 String::format("Could not load repository: %s", e.what()), remote.name()}); 99 return nullptr; 100 } 101 } 102 103 void Transaction::install(const Version *ver, const int flags, 104 const ArchiveReaderPtr &reader) 105 { 106 install(ver, m_registry.getEntry(ver->package()), flags, reader); 107 } 108 109 void Transaction::install(const Version *ver, const Registry::Entry &oldEntry, 110 const int flags, const ArchiveReaderPtr &reader) 111 { 112 m_nextQueue.push(std::make_shared<InstallTask>(ver, flags, oldEntry, reader, this)); 113 } 114 115 void Transaction::setFlags(const Registry::Entry &entry, const int flags) 116 { 117 m_nextQueue.push(std::make_shared<FlagsTask>(entry, flags, this)); 118 } 119 120 void Transaction::uninstall(const Remote &remote) 121 { 122 inhibit(remote); 123 124 const Path &indexPath = Index::pathFor(remote.name()); 125 126 if(FS::exists(indexPath)) { 127 if(!FS::remove(indexPath)) 128 m_receipt.addError({FS::lastError(), indexPath.join()}); 129 } 130 131 for(const auto &entry : m_registry.getEntries(remote.name())) 132 uninstall(entry); 133 } 134 135 void Transaction::uninstall(const Registry::Entry &entry) 136 { 137 m_nextQueue.push(std::make_shared<UninstallTask>(entry, this)); 138 } 139 140 void Transaction::exportArchive(const std::string &path) 141 { 142 m_nextQueue.push(std::make_shared<ExportTask>(path, this)); 143 } 144 145 bool Transaction::runTasks() 146 { 147 do { 148 if(!m_nextQueue.empty()) { 149 m_taskQueues.push(m_nextQueue); 150 TaskQueue().swap(m_nextQueue); 151 } 152 153 if(!commitTasks()) 154 return false; // we're downloading indexes for synchronization 155 else if(m_isCancelled) { 156 finish(); 157 return true; 158 } 159 160 promptObsolete(); 161 162 while(!m_taskQueues.empty()) { 163 runQueue(m_taskQueues.front()); 164 m_taskQueues.pop(); 165 166 if(!commitTasks()) 167 return false; // if the tasks didn't finish immediately (downloading) 168 } 169 } while(!m_nextQueue.empty()); // restart if a task's commit() added new tasks 170 171 finish(); // we're done! 172 173 return true; 174 } 175 176 void Transaction::runQueue(TaskQueue &queue) 177 { 178 m_registry.savepoint(); 179 180 while(!queue.empty()) { 181 const TaskPtr &task = queue.top(); 182 183 if(task->start()) 184 m_runningTasks.push(task); 185 186 queue.pop(); 187 } 188 189 m_registry.restore(); 190 } 191 192 bool Transaction::commitTasks() 193 { 194 // wait until all running tasks are ready 195 if(!m_threadPool.idle()) 196 return false; 197 198 // finish current tasks 199 while(!m_runningTasks.empty()) { 200 if(m_isCancelled) 201 m_runningTasks.front()->rollback(); 202 else 203 m_runningTasks.front()->commit(); 204 205 m_runningTasks.pop(); 206 } 207 208 return true; 209 } 210 211 void Transaction::finish() 212 { 213 m_registry.commit(); 214 registerQueued(); 215 216 onFinish(); 217 m_cleanupHandler(); 218 } 219 220 void Transaction::registerAll(const bool add, const Registry::Entry &entry) 221 { 222 // don't actually do anything until commit() – which will calls registerQueued 223 for(const Registry::File &file : m_registry.getMainFiles(entry)) 224 registerFile({add, entry, file}); 225 } 226 227 void Transaction::registerFile(const HostTicket ®) 228 { 229 if(!AddRemoveReaScript) 230 return; 231 if(reg.file.type != Package::ScriptType || !reg.file.sections) 232 return; 233 234 m_regQueue.push(reg); 235 } 236 237 void Transaction::registerQueued() 238 { 239 while(!m_regQueue.empty()) { 240 const HostTicket ® = m_regQueue.front(); 241 registerScript(reg, m_regQueue.size() == 1); 242 m_regQueue.pop(); 243 } 244 } 245 246 void Transaction::registerScript(const HostTicket ®, const bool isLastCall) 247 { 248 constexpr std::pair<Source::Section, int> sectionMap[] { 249 {Source::MainSection, 0}, 250 {Source::MIDIEditorSection, 32060}, 251 {Source::MIDIEventListEditorSection, 32061}, 252 {Source::MIDIInlineEditorSection, 32062}, 253 {Source::MediaExplorerSection, 32063}, 254 }; 255 256 const std::string &fullPath = reg.file.path.prependRoot().join(); 257 258 std::vector<int> sections; 259 260 for(auto &[flag, id] : sectionMap) { 261 if(reg.file.sections & flag) 262 sections.push_back(id); 263 } 264 265 assert(!sections.empty()); // is a section missing in sectionMap? 266 267 bool enableError = reg.add; 268 auto it = sections.begin(); 269 270 while(true) { 271 const int section = *it++; 272 const bool isLastSection = it == sections.end(); 273 274 const int id = AddRemoveReaScript(reg.add, section, fullPath.c_str(), 275 isLastCall && isLastSection); 276 277 if(!id && enableError) { 278 m_receipt.addError({"This script could not be registered in REAPER.", 279 reg.file.path.join()}); 280 enableError = false; 281 } 282 283 if(isLastSection) 284 break; 285 } 286 } 287 288 void Transaction::inhibit(const Remote &remote) 289 { 290 // prevents index post-download callbacks from being called 291 const auto &it = m_syncedRemotes.find(remote.name()); 292 if(it != m_syncedRemotes.end()) 293 m_syncedRemotes.erase(it); 294 } 295 296 void Transaction::promptObsolete() 297 { 298 if(!g_reapack->config()->install.promptObsolete || m_obsolete.empty()) 299 return; 300 301 std::vector<Registry::Entry> selected; 302 selected.insert(selected.end(), m_obsolete.begin(), m_obsolete.end()); 303 m_obsolete.clear(); 304 305 if(!m_promptObsolete(selected) || selected.empty()) 306 return; 307 308 if(m_taskQueues.empty()) 309 m_taskQueues.push(TaskQueue()); 310 311 for(const auto &entry : selected) 312 m_taskQueues.back().push(std::make_shared<UninstallTask>(entry, this)); 313 }