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 ¤t = 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 }