reapack

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

archive.cpp (8188B)


      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 "archive.hpp"
     19 
     20 #include "config.hpp"
     21 #include "errors.hpp"
     22 #include "filesystem.hpp"
     23 #include "index.hpp"
     24 #include "path.hpp"
     25 #include "reapack.hpp"
     26 #include "transaction.hpp"
     27 #include "win32.hpp"
     28 
     29 #include <fstream>
     30 #include <iomanip>
     31 #include <sstream>
     32 
     33 #include <zip.h>
     34 #include <unzip.h>
     35 #include <ioapi.h>
     36 
     37 static const Path ARCHIVE_TOC("toc");
     38 static const size_t BUFFER_SIZE = 4096;
     39 
     40 #ifdef _WIN32
     41 static void *wide_fopen(voidpf, const void *filename, int mode)
     42 {
     43   const wchar_t *fopen_mode = nullptr;
     44 
     45   if((mode & ZLIB_FILEFUNC_MODE_READWRITEFILTER) == ZLIB_FILEFUNC_MODE_READ)
     46     fopen_mode = L"rb";
     47   else if(mode & ZLIB_FILEFUNC_MODE_EXISTING)
     48     fopen_mode = L"r+b";
     49   else if(mode & ZLIB_FILEFUNC_MODE_CREATE)
     50     fopen_mode = L"wb";
     51 
     52   FILE *file = nullptr;
     53 
     54   if(filename && fopen_mode) {
     55     const auto &&wideFilename = Win32::widen(static_cast<const char *>(filename));
     56     _wfopen_s(&file, wideFilename.c_str(), fopen_mode);
     57   }
     58 
     59   return file;
     60 }
     61 #endif
     62 
     63 struct ImportArchive {
     64   void importRemote(const std::string &);
     65   void importPackage(const std::string &);
     66 
     67   ArchiveReaderPtr m_reader;
     68   RemoteList *m_remotes;
     69   Transaction *m_tx;
     70   IndexPtr m_lastIndex;
     71 };
     72 
     73 void Archive::import(const std::string &path)
     74 {
     75   ImportArchive state{std::make_shared<ArchiveReader>(path),
     76     &g_reapack->config()->remotes};
     77 
     78   std::stringstream toc;
     79   if(const int err = state.m_reader->extractFile(ARCHIVE_TOC, toc))
     80     throw reapack_error(String::format(
     81       "Cannot locate the table of contents (%d)", err));
     82 
     83   // starting import, do not abort process (eg. by throwing) at this point
     84   if(!(state.m_tx = g_reapack->setupTransaction()))
     85     return;
     86 
     87   std::string line;
     88   while(std::getline(toc, line)) {
     89     if(line.size() <= 5) // 5 is the length of the line type prefix
     90       continue;
     91 
     92     const std::string &data = line.substr(5);
     93 
     94     try {
     95       switch(line[0]) {
     96       case 'R':
     97         state.importRemote(data);
     98         break;
     99       case 'P':
    100         state.importPackage(data);
    101         break;
    102       default:
    103         throw reapack_error(String::format("Unknown token '%s' (skipping)",
    104           line.substr(0, 4).c_str()));
    105       }
    106     }
    107     catch(const reapack_error &e) {
    108       state.m_tx->receipt()->addError({e.what(), path});
    109     }
    110   }
    111 
    112   g_reapack->config()->write();
    113   state.m_tx->runTasks();
    114 }
    115 
    116 void ImportArchive::importRemote(const std::string &data)
    117 {
    118   m_lastIndex = nullptr; // clear the previous repository
    119   Remote remote = Remote::fromString(data);
    120 
    121   if(const int err = m_reader->extractFile(Index::pathFor(remote.name()))) {
    122     throw reapack_error(String::format("Failed to extract index of %s (%d)",
    123       remote.name().c_str(), err));
    124   }
    125 
    126   const Remote &original = m_remotes->get(remote.name());
    127   if(original.isProtected()) {
    128     remote.setUrl(original.url());
    129     remote.protect();
    130   }
    131 
    132   m_remotes->add(remote);
    133   m_lastIndex = Index::load(remote.name());
    134 }
    135 
    136 void ImportArchive::importPackage(const std::string &data)
    137 {
    138   // don't report an error if the index isn't loaded assuming we already
    139   // did when failing to import the repository above
    140   if(!m_lastIndex)
    141     return;
    142 
    143   std::string categoryName, packageName, versionName;
    144   int flags;
    145 
    146   std::istringstream stream(data);
    147   stream
    148     >> quoted(categoryName) >> quoted(packageName) >> quoted(versionName)
    149     >> flags;
    150 
    151   const Package *pkg = m_lastIndex->find(categoryName, packageName);
    152   const Version *ver = pkg ? pkg->findVersion(versionName) : nullptr;
    153 
    154   if(!ver) {
    155     throw reapack_error(String::format(
    156       "%s/%s/%s v%s cannot be found or is incompatible with your operating system.",
    157       m_lastIndex->name().c_str(), categoryName.c_str(),
    158       packageName.c_str(), versionName.c_str()));
    159   }
    160 
    161   m_tx->install(ver, flags, m_reader);
    162 }
    163 
    164 ArchiveReader::ArchiveReader(const Path &path)
    165 {
    166   zlib_filefunc64_def filefunc;
    167   fill_fopen64_filefunc(&filefunc);
    168 #ifdef _WIN32
    169   filefunc.zopen64_file = wide_fopen;
    170 #endif
    171 
    172   m_zip = unzOpen2_64(path.join().c_str(), &filefunc);
    173 
    174   if(!m_zip)
    175     throw reapack_error(FS::lastError());
    176 }
    177 
    178 ArchiveReader::~ArchiveReader()
    179 {
    180   unzClose(m_zip);
    181 }
    182 
    183 int ArchiveReader::extractFile(const Path &path)
    184 {
    185   std::ofstream stream;
    186 
    187   if(FS::open(stream, path))
    188     return extractFile(path, stream);
    189   else {
    190     throw reapack_error(String::format("%s: %s",
    191       path.join().c_str(), FS::lastError()));
    192   }
    193 }
    194 
    195 int ArchiveReader::extractFile(const Path &path, std::ostream &stream) noexcept
    196 {
    197   int status = unzLocateFile(m_zip, path.join(false).c_str(), false);
    198   if(status != UNZ_OK)
    199     return status;
    200 
    201   status = unzOpenCurrentFile(m_zip);
    202   if(status != UNZ_OK)
    203     return status;
    204 
    205   std::string buffer(BUFFER_SIZE, 0);
    206 
    207   const auto readChunk = [&] {
    208     return unzReadCurrentFile(m_zip, &buffer[0], static_cast<int>(buffer.size()));
    209   };
    210 
    211   while(const int len = readChunk()) {
    212     if(len < 0)
    213       return len; // read error
    214 
    215     stream.write(&buffer[0], len);
    216   }
    217 
    218   return unzCloseCurrentFile(m_zip);
    219 }
    220 
    221 FileExtractor::FileExtractor(const Path &target, const ArchiveReaderPtr &reader)
    222   : m_path(target), m_reader(reader)
    223 {
    224   setSummary({ "Extracting", target.join() });
    225 }
    226 
    227 bool FileExtractor::run()
    228 {
    229   std::ofstream stream;
    230   if(!FS::open(stream, m_path.temp())) {
    231     setError({FS::lastError(), m_path.temp().join()});
    232     return false;
    233   }
    234 
    235   const int error = m_reader->extractFile(m_path.target(), stream);
    236   stream.close();
    237 
    238   if(error) {
    239     setError({String::format("Failed to extract file (%d)", error),
    240       m_path.target().join()});
    241     return false;
    242   }
    243 
    244   return true;
    245 }
    246 
    247 ArchiveWriter::ArchiveWriter(const Path &path)
    248 {
    249   zlib_filefunc64_def filefunc;
    250   fill_fopen64_filefunc(&filefunc);
    251 #ifdef _WIN32
    252   filefunc.zopen64_file = wide_fopen;
    253 #endif
    254 
    255   m_zip = zipOpen2_64(path.join().c_str(), APPEND_STATUS_CREATE, nullptr, &filefunc);
    256 
    257   if(!m_zip)
    258     throw reapack_error(FS::lastError());
    259 }
    260 
    261 ArchiveWriter::~ArchiveWriter()
    262 {
    263   zipClose(m_zip, nullptr);
    264 }
    265 
    266 int ArchiveWriter::addFile(const Path &path)
    267 {
    268   std::ifstream stream;
    269 
    270   if(FS::open(stream, path))
    271     return addFile(path, stream);
    272   else {
    273     throw reapack_error(String::format("%s: %s",
    274       path.join().c_str(), FS::lastError()));
    275   }
    276 }
    277 
    278 int ArchiveWriter::addFile(const Path &path, std::istream &stream) noexcept
    279 {
    280   const int status = zipOpenNewFileInZip(m_zip, path.join(false).c_str(), nullptr,
    281     nullptr, 0, nullptr, 0, nullptr, Z_DEFLATED, Z_DEFAULT_COMPRESSION);
    282 
    283   if(status != ZIP_OK)
    284     return status;
    285 
    286   std::string buffer(BUFFER_SIZE, 0);
    287 
    288   const auto readChunk = [&] {
    289     stream.read(&buffer[0], buffer.size());
    290     return static_cast<int>(stream.gcount());
    291   };
    292 
    293   while(const int len = readChunk()) {
    294     if(len < 0)
    295       return len; // write error
    296 
    297     zipWriteInFileInZip(m_zip, &buffer[0], len);
    298   }
    299 
    300   return zipCloseFileInZip(m_zip);
    301 }
    302 
    303 FileCompressor::FileCompressor(const Path &target, const ArchiveWriterPtr &writer)
    304   : m_path(target), m_writer(writer)
    305 {
    306   setSummary({ "Compressing", target.join() });
    307 }
    308 
    309 bool FileCompressor::run()
    310 {
    311   std::ifstream stream;
    312   if(!FS::open(stream, m_path)) {
    313     setError({
    314       String::format("Could not open file for export (%s)", FS::lastError()),
    315       m_path.join()});
    316     return false;
    317   }
    318 
    319   const int error = m_writer->addFile(m_path, stream);
    320   stream.close();
    321 
    322   if(error) {
    323     setError({String::format("Failed to compress file (%d)", error), m_path.join()});
    324     return false;
    325   }
    326 
    327   return true;
    328 }