install.cpp (3933B)
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 "task.hpp" 19 20 #include "archive.hpp" 21 #include "config.hpp" 22 #include "download.hpp" 23 #include "filesystem.hpp" 24 #include "index.hpp" 25 #include "reapack.hpp" 26 #include "transaction.hpp" 27 28 InstallTask::InstallTask(const Version *ver, const int flags, 29 const Registry::Entry &re, const ArchiveReaderPtr &reader, Transaction *tx) 30 : Task(tx), m_version(ver), m_flags(flags), m_oldEntry(std::move(re)), m_reader(reader), 31 m_fail(false), m_index(ver->package()->category()->index()->shared_from_this()) 32 { 33 } 34 35 bool InstallTask::start() 36 { 37 // get current files before overwriting the entry 38 m_oldFiles = tx()->registry()->getFiles(m_oldEntry); 39 40 // prevent file conflicts (don't worry, the registry push is reverted) 41 try { 42 std::vector<Path> conflicts; 43 tx()->registry()->push(m_version, m_flags, &conflicts); 44 45 if(!conflicts.empty()) { 46 for(const Path &path : conflicts) { 47 tx()->receipt()->addError({"Conflict: " + path.join() + 48 " is already owned by another package", m_version->fullName()}); 49 } 50 51 return false; 52 } 53 } 54 catch(const reapack_error &e) { 55 tx()->receipt()->addError({e.what(), m_version->fullName()}); 56 return false; 57 } 58 59 for(const Source *src : m_version->sources()) { 60 const Path &targetPath = src->targetPath(); 61 62 const auto &old = find_if(m_oldFiles.begin(), m_oldFiles.end(), 63 [&](const Registry::File &f) { return f.path == targetPath; }); 64 65 if(old != m_oldFiles.end()) 66 m_oldFiles.erase(old); 67 68 if(m_reader) { 69 FileExtractor *ex = new FileExtractor(targetPath, m_reader); 70 push(ex, ex->path()); 71 } 72 else { 73 const NetworkOpts &opts = g_reapack->config()->network; 74 FileDownload *dl = new FileDownload(targetPath, src->url(), opts); 75 dl->setExpectedChecksum(src->checksum()); 76 push(dl, dl->path()); 77 } 78 } 79 80 return true; 81 } 82 83 void InstallTask::push(ThreadTask *job, const TempPath &path) 84 { 85 job->onStartAsync >> [=] { m_newFiles.push_back(path); }; 86 job->onFinishAsync >> [=] { 87 m_waiting.erase(job); 88 89 if(job->state() != ThreadTask::Success) 90 rollback(); 91 }; 92 93 m_waiting.insert(job); 94 tx()->threadPool()->push(job); 95 } 96 97 void InstallTask::commit() 98 { 99 if(m_fail) 100 return; 101 102 for(const TempPath &paths : m_newFiles) { 103 if(!FS::rename(paths)) { 104 tx()->receipt()->addError({ 105 String::format("Cannot rename to target: %s", FS::lastError()), 106 paths.target().join()}); 107 108 // it's a bit late to rollback here as some files might already have been 109 // overwritten. at least we can delete the temporary files 110 rollback(); 111 return; 112 } 113 } 114 115 for(const Registry::File &file : m_oldFiles) { 116 if(FS::remove(file.path)) 117 tx()->receipt()->addRemoval(file.path); 118 119 tx()->registerFile({false, m_oldEntry, file}); 120 } 121 122 tx()->receipt()->addInstall(m_version, m_oldEntry); 123 124 const Registry::Entry &newEntry = tx()->registry()->push(m_version, m_flags); 125 tx()->registerAll(true, newEntry); 126 } 127 128 void InstallTask::rollback() 129 { 130 for(const TempPath &paths : m_newFiles) 131 FS::removeRecursive(paths.temp()); 132 133 for(ThreadTask *job : m_waiting) 134 job->abort(); 135 136 m_fail = true; 137 }