reapack

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

registry.cpp (9600B)


      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 "registry.hpp"
     19 
     20 #include "errors.hpp"
     21 #include "index.hpp"
     22 #include "package.hpp"
     23 #include "path.hpp"
     24 #include "remote.hpp"
     25 
     26 #include <algorithm>
     27 
     28 #include <sqlite3.h>
     29 
     30 Registry::Registry(const Path &path)
     31   : m_db(path.join())
     32 {
     33   migrate();
     34 
     35   // entry queries
     36   m_insertEntry = m_db.prepare(
     37     "INSERT INTO entries(remote, category, package, desc, type, version, author, flags)"
     38     "VALUES(?, ?, ?, ?, ?, ?, ?, ?);"
     39   );
     40 
     41   m_updateEntry = m_db.prepare(
     42     "UPDATE entries "
     43     "SET desc = ?, type = ?, version = ?, author = ?, flags = ? WHERE id = ?"
     44   );
     45 
     46   m_setFlags = m_db.prepare("UPDATE entries SET flags = ? WHERE id = ?");
     47 
     48   m_findEntry = m_db.prepare(
     49     "SELECT id, remote, category, package, desc, type, version, author, flags "
     50     "FROM entries WHERE remote = ? AND category = ? AND package = ? LIMIT 1"
     51   );
     52 
     53   m_allEntries = m_db.prepare(
     54     "SELECT id, remote, category, package, desc, type, version, author, flags "
     55     "FROM entries WHERE remote = ?"
     56   );
     57   m_forgetEntry = m_db.prepare("DELETE FROM entries WHERE id = ?");
     58 
     59   // file queries
     60   m_getOwner = m_db.prepare(
     61     "SELECT e.id, remote, category, package, desc, e.type, version, author, flags "
     62     "FROM entries e JOIN files f ON f.entry = e.id WHERE f.path = ? LIMIT 1"
     63   );
     64   m_getFiles = m_db.prepare(
     65     "SELECT path, main, type FROM files WHERE entry = ? ORDER BY path"
     66   );
     67   m_insertFile = m_db.prepare("INSERT INTO files VALUES(NULL, ?, ?, ?, ?)");
     68   m_clearFiles = m_db.prepare(
     69     "DELETE FROM files WHERE entry = ("
     70     "  SELECT id FROM entries WHERE remote = ? AND category = ? AND package = ?"
     71     ")"
     72   );
     73   m_forgetFiles = m_db.prepare("DELETE FROM files WHERE entry = ?");
     74 
     75   // lock the database
     76   m_db.begin();
     77 }
     78 
     79 void Registry::migrate()
     80 {
     81   const Database::Version version{0, 6};
     82   const Database::Version &current = m_db.version();
     83 
     84   if(!current) {
     85     // new database!
     86     m_db.exec(
     87       "CREATE TABLE entries ("
     88       "  id INTEGER PRIMARY KEY,"
     89       "  remote TEXT NOT NULL,"
     90       "  category TEXT NOT NULL,"
     91       "  package TEXT NOT NULL,"
     92       "  desc TEXT NOT NULL,"
     93       "  type INTEGER NOT NULL,"
     94       "  version TEXT NOT NULL,"
     95       "  author TEXT NOT NULL,"
     96       "  flags INTEGER DEFAULT 0,"
     97       "  UNIQUE(remote, category, package)"
     98       ");"
     99 
    100       "CREATE TABLE files ("
    101       "  id INTEGER PRIMARY KEY,"
    102       "  entry INTEGER NOT NULL,"
    103       "  path TEXT UNIQUE NOT NULL,"
    104       "  main INTEGER NOT NULL,"
    105       "  type INTEGER NOT NULL,"
    106       "  FOREIGN KEY(entry) REFERENCES entries(id)"
    107       ");"
    108     );
    109 
    110     m_db.setVersion(version);
    111 
    112     return;
    113   }
    114   else if(current < version) {
    115     m_db.begin();
    116 
    117     switch(current.major) {
    118     case 0:
    119       // current major schema version
    120       break;
    121     default:
    122       throw reapack_error(
    123         "The package registry was created by a newer version of ReaPack");
    124     }
    125 
    126     switch(current.minor) {
    127     case 1:
    128       m_db.exec("ALTER TABLE entries ADD COLUMN pinned INTEGER NOT NULL DEFAULT 0;");
    129       [[fallthrough]];
    130     case 2:
    131       m_db.exec("ALTER TABLE files ADD COLUMN type INTEGER NOT NULL DEFAULT 0;");
    132       [[fallthrough]];
    133     case 3:
    134       m_db.exec("ALTER TABLE entries ADD COLUMN desc TEXT NOT NULL DEFAULT '';");
    135       [[fallthrough]];
    136     case 4:
    137       convertImplicitSections();
    138       [[fallthrough]];
    139     case 5:
    140       m_db.exec("ALTER TABLE entries RENAME COLUMN pinned TO flags;");
    141       break;
    142     }
    143 
    144     m_db.setVersion(version);
    145     m_db.commit();
    146   }
    147 }
    148 
    149 auto Registry::push(const Version *ver, const int flags,
    150   std::vector<Path> *conflicts) -> Entry
    151 {
    152   m_db.savepoint();
    153 
    154   bool hasConflicts = false;
    155 
    156   const Package *pkg = ver->package();
    157   const Category *cat = pkg->category();
    158   const Index *ri = cat->index();
    159 
    160   m_clearFiles->bind(1, ri->name());
    161   m_clearFiles->bind(2, cat->name());
    162   m_clearFiles->bind(3, pkg->name());
    163   m_clearFiles->exec();
    164 
    165   auto entryId = getEntry(ver->package()).id;
    166 
    167   // register or update package and version
    168   if(entryId) {
    169     int col = 1;
    170     m_updateEntry->bind(col++, pkg->description());
    171     m_updateEntry->bind(col++, pkg->type());
    172     m_updateEntry->bind(col++, ver->name().toString());
    173     m_updateEntry->bind(col++, ver->author());
    174     m_updateEntry->bind(col++, flags);
    175     m_updateEntry->bind(col++, entryId);
    176     m_updateEntry->exec();
    177   }
    178   else {
    179     int col = 1;
    180     m_insertEntry->bind(col++, ri->name());
    181     m_insertEntry->bind(col++, cat->name());
    182     m_insertEntry->bind(col++, pkg->name());
    183     m_insertEntry->bind(col++, pkg->description());
    184     m_insertEntry->bind(col++, pkg->type());
    185     m_insertEntry->bind(col++, ver->name().toString());
    186     m_insertEntry->bind(col++, ver->author());
    187     m_insertEntry->bind(col++, flags);
    188     m_insertEntry->exec();
    189 
    190     entryId = m_db.lastInsertId();
    191   }
    192 
    193   // register files
    194   for(const Source *src : ver->sources()) {
    195     const Path &path = src->targetPath();
    196 
    197     int col = 1;
    198     m_insertFile->bind(col++, entryId);
    199     m_insertFile->bind(col++, path.join(false));
    200     m_insertFile->bind(col++, src->sections());
    201     m_insertFile->bind(col++, src->typeOverride());
    202 
    203     try {
    204       m_insertFile->exec();
    205     }
    206     catch(const reapack_error &) {
    207       if(conflicts && m_db.errorCode() == SQLITE_CONSTRAINT) {
    208         hasConflicts = true;
    209         conflicts->push_back(path);
    210       }
    211       else {
    212         m_db.restore();
    213         throw;
    214       }
    215     }
    216   }
    217 
    218   if(hasConflicts) {
    219     m_db.restore();
    220     return {};
    221   }
    222   else {
    223     m_db.release();
    224     return {
    225       entryId, ri->name(), cat->name(), pkg->name(), pkg->description(),
    226       pkg->type(), ver->name(), ver->author(), flags
    227     };
    228   }
    229 }
    230 
    231 void Registry::setFlags(const Entry &entry, const int flags)
    232 {
    233   m_setFlags->bind(1, flags);
    234   m_setFlags->bind(2, entry.id);
    235   m_setFlags->exec();
    236 }
    237 
    238 auto Registry::getEntry(const Package *pkg) const -> Entry
    239 {
    240   Entry entry{};
    241 
    242   const Category *cat = pkg->category();
    243   const Index *ri = cat->index();
    244 
    245   m_findEntry->bind(1, ri->name());
    246   m_findEntry->bind(2, cat->name());
    247   m_findEntry->bind(3, pkg->name());
    248 
    249   m_findEntry->exec([&] {
    250     fillEntry(m_findEntry, &entry);
    251     return false;
    252   });
    253 
    254   return entry;
    255 }
    256 
    257 auto Registry::getEntries(const std::string &remoteName) const -> std::vector<Entry>
    258 {
    259   std::vector<Registry::Entry> list;
    260 
    261   m_allEntries->bind(1, remoteName);
    262   m_allEntries->exec([&] {
    263     Entry entry{};
    264     fillEntry(m_allEntries, &entry);
    265     list.push_back(entry);
    266 
    267     return true;
    268   });
    269 
    270   return list;
    271 }
    272 
    273 auto Registry::getFiles(const Entry &entry) const -> std::vector<File>
    274 {
    275   if(!entry) // skip processing for new packages
    276     return {};
    277 
    278   std::vector<File> files;
    279 
    280   m_getFiles->bind(1, entry.id);
    281   m_getFiles->exec([&] {
    282     int col = 0;
    283 
    284     File file{
    285       m_getFiles->stringColumn(col++),
    286       static_cast<int>(m_getFiles->intColumn(col++)),
    287       static_cast<Package::Type>(m_getFiles->intColumn(col++)),
    288     };
    289 
    290     if(!file.type) // < v1.0rc2
    291       file.type = entry.type;
    292 
    293     files.push_back(file);
    294     return true;
    295   });
    296 
    297   return files;
    298 }
    299 
    300 auto Registry::getMainFiles(const Entry &entry) const -> std::vector<File>
    301 {
    302   if(!entry)
    303     return {};
    304 
    305   const std::vector<File> &allFiles = getFiles(entry);
    306 
    307   std::vector<File> mainFiles;
    308   std::copy_if(allFiles.begin(), allFiles.end(),
    309     std::back_inserter(mainFiles), [&](const File &f) { return f.sections; });
    310 
    311   return mainFiles;
    312 }
    313 
    314 auto Registry::getOwner(const Path &path) const -> Entry
    315 {
    316   Entry entry{};
    317 
    318   m_getOwner->bind(1, path.join(false));
    319 
    320   m_getOwner->exec([&] {
    321     fillEntry(m_getOwner, &entry);
    322     return false;
    323   });
    324 
    325   return entry;
    326 }
    327 
    328 void Registry::forget(const Entry &entry)
    329 {
    330   m_forgetFiles->bind(1, entry.id);
    331   m_forgetFiles->exec();
    332 
    333   m_forgetEntry->bind(1, entry.id);
    334   m_forgetEntry->exec();
    335 }
    336 
    337 void Registry::convertImplicitSections()
    338 {
    339   // convert from v1.0 main=true format to v1.1 flag format
    340 
    341   Statement entries("SELECT id, category FROM entries", &m_db);
    342   entries.exec([&] {
    343     const auto id = entries.intColumn(0);
    344     const std::string &category = entries.stringColumn(1);
    345     const auto section = Source::detectSection(category);
    346 
    347     Statement update("UPDATE files SET main = ? WHERE entry = ? AND main != 0", &m_db);
    348     update.bind(1, section);
    349     update.bind(2, id);
    350     update.exec();
    351 
    352     return true;
    353   });
    354 }
    355 
    356 void Registry::fillEntry(const Statement *stmt, Entry *entry) const
    357 {
    358   int col = 0;
    359 
    360   entry->id = stmt->intColumn(col++);
    361   entry->remote = stmt->stringColumn(col++);
    362   entry->category = stmt->stringColumn(col++);
    363   entry->package = stmt->stringColumn(col++);
    364   entry->description = stmt->stringColumn(col++);
    365   entry->type = static_cast<Package::Type>(stmt->intColumn(col++));
    366   entry->version.tryParse(stmt->stringColumn(col++));
    367   entry->author = stmt->stringColumn(col++);
    368   entry->flags = static_cast<int>(stmt->intColumn(col++));
    369 }