reapack

Package manager for REAPER
Log | Files | Refs | Submodules | README | LICENSE

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 &reg)
    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 &reg = m_regQueue.front();
    241     registerScript(reg, m_regQueue.size() == 1);
    242     m_regQueue.pop();
    243   }
    244 }
    245 
    246 void Transaction::registerScript(const HostTicket &reg, 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 }