db.cpp (48755B)
1 #include "db.h" 2 3 #include <cassert> 4 5 #include "datasource.h" 6 #include "patch.h" 7 #include "patchmodifications.h" 8 9 #include "synthLib/midiToSysex.h" 10 11 #include "baseLib/hybridcontainer.h" 12 #include "baseLib/binarystream.h" 13 #include "baseLib/filesystem.h" 14 15 #include "dsp56kEmu/logging.h" 16 17 namespace pluginLib::patchDB 18 { 19 constexpr const char* g_fileNameCache = "patchmanagerdb.cache"; 20 constexpr const char* g_filenameJson = "patchmanagerdb.json"; 21 22 static constexpr bool g_cacheEnabled = true; 23 24 DB::DB(juce::File _dir) 25 : m_settingsDir(std::move(_dir)) 26 , m_loader("PatchLoader", false, dsp56k::ThreadPriority::Lowest) 27 { 28 m_settingsDir.createDirectory(); 29 } 30 31 DB::~DB() 32 { 33 assert(m_loader.destroyed() && "stopLoaderThread() needs to be called by derived class in destructor"); 34 35 // we can only save the cache if the db is not doing anything in the background 36 const bool loading = !m_loader.empty(); 37 38 stopLoaderThread(); 39 40 if(!loading && m_cacheDirty && g_cacheEnabled) 41 saveCache(); 42 } 43 44 DataSourceNodePtr DB::addDataSource(const DataSource& _ds, const DataSourceLoadedCallback& _callback) 45 { 46 return addDataSource(_ds, true, _callback); 47 } 48 49 bool DB::writePatchesToFile(const juce::File& _file, const std::vector<PatchPtr>& _patches) 50 { 51 std::vector<uint8_t> sysexBuffer; 52 sysexBuffer.reserve(_patches.front()->sysex.size() * _patches.size()); 53 54 for (const auto& patch : _patches) 55 { 56 const auto patchSysex = patch->sysex; 57 58 if(!patchSysex.empty()) 59 sysexBuffer.insert(sysexBuffer.end(), patchSysex.begin(), patchSysex.end()); 60 } 61 62 if(!_file.replaceWithData(sysexBuffer.data(), sysexBuffer.size())) 63 { 64 pushError("Failed to write to file " + _file.getFullPathName().toStdString() + ", make sure that it is not write protected"); 65 return false; 66 } 67 return true; 68 } 69 70 void DB::assign(const PatchPtr& _patch, const PatchModificationsPtr& _mods) 71 { 72 if(!_patch || !_mods) 73 return; 74 75 _patch->modifications = _mods; 76 _mods->patch = _patch; 77 _mods->updateCache(); 78 } 79 80 std::string DB::createValidFilename(const std::string& _name) 81 { 82 std::string result; 83 result.reserve(_name.size()); 84 85 for (const char c : _name) 86 { 87 if ((c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z')) 88 result += c; 89 else 90 result += '_'; 91 } 92 return result; 93 } 94 95 DataSourceNodePtr DB::addDataSource(const DataSource& _ds, const bool _save, const DataSourceLoadedCallback& _callback) 96 { 97 const auto needsSave = _save && _ds.origin == DataSourceOrigin::Manual && _ds.type != SourceType::Rom; 98 99 auto ds = std::make_shared<DataSourceNode>(_ds); 100 101 runOnLoaderThread([this, ds, needsSave, _callback] 102 { 103 addDataSource(ds); 104 105 runOnUiThread([ds, _callback] 106 { 107 _callback(true, ds); 108 }); 109 110 if(needsSave) 111 saveJson(); 112 }); 113 114 return ds; 115 } 116 117 void DB::removeDataSource(const DataSource& _ds, bool _save/* = true*/) 118 { 119 runOnLoaderThread([this, _ds, _save] 120 { 121 std::unique_lock lockDs(m_dataSourcesMutex); 122 123 const auto it = m_dataSources.find(_ds); 124 if (it == m_dataSources.end()) 125 return; 126 127 const auto ds = it->second; 128 129 // if a DS is removed that is of type Manual, and it has a parent, switch it to Autogenerated but don't remove it 130 if (ds->origin == DataSourceOrigin::Manual && ds->hasParent()) 131 { 132 ds->origin = DataSourceOrigin::Autogenerated; 133 134 std::unique_lock lockUi(m_uiMutex); 135 m_dirty.dataSources = true; 136 return; 137 } 138 139 std::set<DataSourceNodePtr> removedDataSources{it->second}; 140 std::vector<PatchPtr> removedPatches; 141 142 // remove all datasources that are a child of the one being removed 143 std::function<void(const DataSourceNodePtr&)> removeChildren = [&](const DataSourceNodePtr& _parent) 144 { 145 for (auto& child : _parent->getChildren()) 146 { 147 const auto c = child.lock(); 148 149 if (!c || c->origin == DataSourceOrigin::Manual) 150 continue; 151 152 removedDataSources.insert(c); 153 removeChildren(c); 154 } 155 }; 156 157 removeChildren(ds); 158 159 for (const auto& removed : removedDataSources) 160 { 161 removedPatches.insert(removedPatches.end(), removed->patches.begin(), removed->patches.end()); 162 m_dataSources.erase(*removed); 163 } 164 165 lockDs.unlock(); 166 167 const auto patchesChanged = !removedPatches.empty(); 168 169 removePatchesFromSearches(removedPatches); 170 171 { 172 std::unique_lock pl(m_patchesMutex); 173 preservePatchModifications(removedPatches); 174 } 175 176 { 177 std::unique_lock lockUi(m_uiMutex); 178 179 m_dirty.dataSources = true; 180 if (patchesChanged) 181 m_dirty.patches = true; 182 } 183 184 for (auto& removedDataSource : removedDataSources) 185 { 186 removedDataSource->setParent(nullptr); 187 removedDataSource->removeAllChildren(); 188 removedDataSource->patches.clear(); 189 } 190 removedDataSources.clear(); 191 192 if(_save) 193 saveJson(); 194 }); 195 } 196 197 void DB::refreshDataSource(const DataSourceNodePtr& _ds) 198 { 199 auto parent = _ds->getParent(); 200 201 removeDataSource(*_ds, false); 202 203 runOnLoaderThread([this, parent, _ds] 204 { 205 _ds->setParent(parent); 206 addDataSource(_ds); 207 saveJson(); 208 }); 209 } 210 211 void DB::renameDataSource(const DataSourceNodePtr& _ds, const std::string& _newName) 212 { 213 if(_ds->type != SourceType::LocalStorage) 214 return; 215 216 if(_newName.empty()) 217 return; 218 219 runOnLoaderThread([this, _ds, _newName] 220 { 221 juce::File oldFile; 222 juce::File oldJsonFile; 223 224 { 225 std::unique_lock lockDs(m_dataSourcesMutex); 226 const auto it = m_dataSources.find(*_ds); 227 228 if(it == m_dataSources.end()) 229 return; 230 231 const auto ds = it->second; 232 233 if(ds->name == _newName) 234 return; 235 236 for (const auto& [_, d] : m_dataSources) 237 { 238 if(d->type == SourceType::LocalStorage && d->name == _newName) 239 return; 240 } 241 242 oldFile = getLocalStorageFile(*ds); 243 oldJsonFile = getJsonFile(*ds); 244 245 ds->name = _newName; 246 247 m_dataSources.erase(it); 248 m_dataSources.insert({*ds, ds}); 249 } 250 251 std::unique_lock lockUi(m_uiMutex); 252 m_dirty.dataSources = true; 253 254 saveJson(); 255 256 deleteFile(oldFile); 257 deleteFile(oldJsonFile); 258 }); 259 } 260 261 DataSourceNodePtr DB::getDataSource(const DataSource& _ds) 262 { 263 std::shared_lock lock(m_dataSourcesMutex); 264 const auto it = m_dataSources.find(_ds); 265 if(it == m_dataSources.end()) 266 return {}; 267 return it->second; 268 } 269 270 std::set<DataSourceNodePtr> DB::getDataSourcesOfSourceType(const SourceType _type) 271 { 272 std::set<DataSourceNodePtr> results; 273 274 { 275 std::shared_lock lockDS(m_dataSourcesMutex); 276 for (const auto& ds : m_dataSources) 277 { 278 if(ds.second->type == _type) 279 results.insert(ds.second); 280 } 281 } 282 283 return results; 284 } 285 286 bool DB::setTagColor(const TagType _type, const Tag& _tag, const Color _color) 287 { 288 std::shared_lock lock(m_patchesMutex); 289 if(_color == g_invalidColor) 290 { 291 const auto itType = m_tagColors.find(_type); 292 293 if(itType == m_tagColors.end()) 294 return false; 295 296 if(!itType->second.erase(_tag)) 297 return false; 298 } 299 else 300 { 301 if(m_tagColors[_type][_tag] == _color) 302 return false; 303 m_tagColors[_type][_tag] = _color; 304 } 305 306 std::unique_lock lockUi(m_uiMutex); 307 m_dirty.tags.insert(_type); 308 309 // TODO: this might spam saving if this function is called too often 310 runOnLoaderThread([this] 311 { 312 saveJson(); 313 }); 314 return true; 315 } 316 317 Color DB::getTagColor(const TagType _type, const Tag& _tag) const 318 { 319 std::shared_lock lock(m_patchesMutex); 320 return getTagColorInternal(_type, _tag); 321 } 322 323 Color DB::getPatchColor(const PatchPtr& _patch, const TypedTags& _tagsToIgnore) const 324 { 325 const auto& tags = _patch->getTags(); 326 327 for (const auto& itType : tags.get()) 328 { 329 for (const auto& tag : itType.second.getAdded()) 330 { 331 if(_tagsToIgnore.containsAdded(itType.first, tag)) 332 continue; 333 334 const auto c = getTagColor(itType.first, tag); 335 if(c != g_invalidColor) 336 return c; 337 } 338 } 339 340 return g_invalidColor; 341 } 342 343 bool DB::addTag(const TagType _type, const std::string& _tag) 344 { 345 { 346 std::unique_lock lock(m_patchesMutex); 347 if (!internalAddTag(_type, _tag)) 348 return false; 349 } 350 saveJson(); 351 return true; 352 } 353 354 bool DB::removeTag(TagType _type, const Tag& _tag) 355 { 356 { 357 std::unique_lock lock(m_patchesMutex); 358 if (!internalRemoveTag(_type, _tag)) 359 return false; 360 } 361 saveJson(); 362 return true; 363 } 364 365 void DB::uiProcess() 366 { 367 std::list<std::function<void()>> uiFuncs; 368 369 Dirty dirty; 370 { 371 std::scoped_lock lock(m_uiMutex); 372 std::swap(uiFuncs, m_uiFuncs); 373 std::swap(dirty, m_dirty); 374 } 375 376 processDirty(dirty); 377 378 for (const auto& func : uiFuncs) 379 func(); 380 381 if(isLoading() && !m_loader.pending()) 382 { 383 m_loading = false; 384 onLoadFinished(); 385 } 386 } 387 388 uint32_t DB::search(SearchRequest&& _request, SearchCallback&& _callback) 389 { 390 const auto handle = m_nextSearchHandle++; 391 392 auto s = std::make_shared<Search>(); 393 394 s->handle = handle; 395 s->request = std::move(_request); 396 s->callback = std::move(_callback); 397 398 { 399 std::unique_lock lock(m_searchesMutex); 400 m_searches.insert({ s->handle, s }); 401 } 402 403 runOnLoaderThread([this, s] 404 { 405 executeSearch(*s); 406 }); 407 408 return handle; 409 } 410 411 SearchHandle DB::findDatasourceForPatch(const PatchPtr& _patch, SearchCallback&& _callback) 412 { 413 SearchRequest req; 414 req.patch = _patch; 415 return search(std::move(req), std::move(_callback)); 416 } 417 418 void DB::cancelSearch(const uint32_t _handle) 419 { 420 if(_handle == g_invalidSearchHandle) 421 return; 422 423 std::unique_lock lock(m_searchesMutex); 424 m_cancelledSearches.insert(_handle); 425 m_searches.erase(_handle); 426 } 427 428 std::shared_ptr<Search> DB::getSearch(const SearchHandle _handle) 429 { 430 std::shared_lock lock(m_searchesMutex); 431 const auto it = m_searches.find(_handle); 432 if (it == m_searches.end()) 433 return {}; 434 return it->second; 435 } 436 437 std::shared_ptr<Search> DB::getSearch(const DataSource& _dataSource) 438 { 439 std::shared_lock lock(m_searchesMutex); 440 441 for (const auto& it : m_searches) 442 { 443 const auto& search = it.second; 444 if(!search->request.sourceNode) 445 continue; 446 if(*search->request.sourceNode == _dataSource) 447 return search; 448 } 449 return nullptr; 450 } 451 452 void DB::copyPatchesTo(const DataSourceNodePtr& _ds, const std::vector<PatchPtr>& _patches, int _insertRow/* = -1*/, const std::function<void(const std::vector<PatchPtr>&)>& _successCallback/* = {}*/) 453 { 454 if (_ds->type != SourceType::LocalStorage) 455 return; 456 457 runOnLoaderThread([this, _ds, _patches, _insertRow, _successCallback] 458 { 459 { 460 std::shared_lock lockDs(m_dataSourcesMutex); 461 const auto itDs = m_dataSources.find(*_ds); 462 if (itDs == m_dataSources.end()) 463 return; 464 } 465 466 // filter out all patches that are already part of _ds 467 std::vector<PatchPtr> patchesToAdd; 468 patchesToAdd.reserve(_patches.size()); 469 470 for (const auto& patch : _patches) 471 { 472 if (_ds->contains(patch)) 473 continue; 474 475 patchesToAdd.push_back(patch); 476 } 477 478 if(patchesToAdd.empty()) 479 return; 480 481 std::vector<PatchPtr> newPatches; 482 newPatches.reserve(patchesToAdd.size()); 483 484 uint32_t newPatchProgramNumber = _insertRow >= 0 ? static_cast<uint32_t>(_insertRow) : _ds->getMaxProgramNumber() + 1; 485 486 if(newPatchProgramNumber > _ds->getMaxProgramNumber() + 1) 487 newPatchProgramNumber = _ds->getMaxProgramNumber() + 1; 488 489 _ds->makeSpaceForNewPatches(newPatchProgramNumber, static_cast<uint32_t>(patchesToAdd.size())); 490 491 for (const auto& patch : patchesToAdd) 492 { 493 auto [newPatch, newMods] = patch->createCopy(_ds); 494 495 newPatch->program = newPatchProgramNumber++; 496 497 newPatches.push_back(newPatch); 498 } 499 500 addPatches(newPatches); 501 502 createConsecutiveProgramNumbers(_ds); 503 504 if(_successCallback) 505 { 506 runOnUiThread([_successCallback, newPatches] 507 { 508 _successCallback(newPatches); 509 }); 510 } 511 512 saveJson(); 513 }); 514 } 515 516 void DB::removePatches(const DataSourceNodePtr& _ds, const std::vector<PatchPtr>& _patches) 517 { 518 if (_ds->type != SourceType::LocalStorage) 519 return; 520 521 runOnLoaderThread([this, _ds, _patches] 522 { 523 { 524 std::shared_lock lockDs(m_dataSourcesMutex); 525 const auto itDs = m_dataSources.find(*_ds); 526 if (itDs == m_dataSources.end()) 527 return; 528 } 529 530 { 531 std::vector<PatchPtr> removedPatches; 532 removedPatches.reserve(_patches.size()); 533 534 std::unique_lock lock(m_patchesMutex); 535 536 for (const auto& patch : _patches) 537 { 538 if(_ds->patches.erase(patch)) 539 removedPatches.emplace_back(patch); 540 } 541 542 if (removedPatches.empty()) 543 return; 544 545 removePatchesFromSearches(removedPatches); 546 preservePatchModifications(removedPatches); 547 548 { 549 std::unique_lock lockUi(m_uiMutex); 550 m_dirty.patches = true; 551 } 552 } 553 554 saveJson(); 555 }); 556 } 557 558 bool DB::movePatchesTo(const uint32_t _position, const std::vector<PatchPtr>& _patches) 559 { 560 if(_patches.empty()) 561 return false; 562 563 { 564 std::unique_lock lock(m_patchesMutex); 565 566 const auto ds = _patches.front()->source.lock(); 567 568 if(!ds || ds->type != SourceType::LocalStorage) 569 return false; 570 571 if(!ds->movePatchesTo(_position, _patches)) 572 return false; 573 } 574 575 { 576 std::unique_lock lockUi(m_uiMutex); 577 m_dirty.dataSources = true; 578 } 579 580 runOnLoaderThread([this] 581 { 582 saveJson(); 583 }); 584 585 return true; 586 } 587 588 bool DB::isValid(const PatchPtr& _patch) 589 { 590 if (!_patch) 591 return false; 592 if (_patch->getName().empty()) 593 return false; 594 if (_patch->sysex.empty()) 595 return false; 596 if (_patch->sysex.front() != 0xf0) 597 return false; 598 if (_patch->sysex.back() != 0xf7) 599 return false; 600 return true; 601 } 602 603 PatchPtr DB::requestPatchForPart(const uint32_t _part, const uint64_t _userData) 604 { 605 Data data; 606 requestPatchForPart(data, _part, _userData); 607 return initializePatch(std::move(data), {}); 608 } 609 610 void DB::getTags(const TagType _type, std::set<Tag>& _tags) 611 { 612 _tags.clear(); 613 614 std::shared_lock lock(m_patchesMutex); 615 const auto it = m_tags.find(_type); 616 if (it == m_tags.end()) 617 return; 618 619 _tags = it->second; 620 } 621 622 bool DB::modifyTags(const std::vector<PatchPtr>& _patches, const TypedTags& _tags) 623 { 624 if(_tags.empty()) 625 return false; 626 627 std::vector<PatchPtr> changed; 628 changed.reserve(_patches.size()); 629 630 std::unique_lock lock(m_patchesMutex); 631 632 for (const auto& patch : _patches) 633 { 634 if(patch->source.expired()) 635 continue; 636 637 auto mods = patch->modifications; 638 639 if(!mods) 640 { 641 mods = std::make_shared<PatchModifications>(); 642 assign(patch, mods); 643 } 644 645 if (!mods->modifyTags(_tags)) 646 continue; 647 648 changed.push_back(patch); 649 } 650 651 if(!changed.empty()) 652 { 653 updateSearches(changed); 654 } 655 656 lock.unlock(); 657 658 if(!changed.empty()) 659 saveJson(); 660 661 return true; 662 } 663 664 bool DB::renamePatch(const PatchPtr& _patch, const std::string& _name) 665 { 666 if(_patch->getName() == _name) 667 return false; 668 669 if(_name.empty()) 670 return false; 671 672 { 673 std::unique_lock lock(m_patchesMutex); 674 675 const auto ds = _patch->source.lock(); 676 if(!ds) 677 return false; 678 679 auto mods = _patch->modifications; 680 if(!mods) 681 { 682 mods = std::make_shared<PatchModifications>(); 683 assign(_patch, mods); 684 } 685 686 mods->name = _name; 687 688 updateSearches({_patch}); 689 } 690 691 runOnLoaderThread([this] 692 { 693 saveJson(); 694 }); 695 696 return true; 697 } 698 699 bool DB::replacePatch(const PatchPtr& _existing, const PatchPtr& _new) 700 { 701 if(!_existing || !_new) 702 return false; 703 704 if(_existing == _new) 705 return false; 706 707 const auto ds = _existing->source.lock(); 708 709 if(!ds || ds->type != SourceType::LocalStorage) 710 return false; 711 712 std::unique_lock lock(m_patchesMutex); 713 714 _existing->replaceData(*_new); 715 716 if(_existing->modifications) 717 _existing->modifications->name.clear(); 718 719 updateSearches({_existing}); 720 721 runOnLoaderThread([this] 722 { 723 saveJson(); 724 }); 725 726 return true; 727 } 728 729 SearchHandle DB::search(SearchRequest&& _request) 730 { 731 return search(std::move(_request), [](const Search&) {}); 732 } 733 734 bool DB::loadData(DataList& _results, const DataSourceNodePtr& _ds) 735 { 736 return loadData(_results, *_ds); 737 } 738 739 bool DB::loadData(DataList& _results, const DataSource& _ds) 740 { 741 switch (_ds.type) 742 { 743 case SourceType::Rom: 744 return loadRomData(_results, _ds.bank, g_invalidProgram); 745 case SourceType::File: 746 return loadFile(_results, _ds.name); 747 case SourceType::Invalid: 748 case SourceType::Folder: 749 case SourceType::Count: 750 return false; 751 case SourceType::LocalStorage: 752 return loadLocalStorage(_results, _ds); 753 } 754 return false; 755 } 756 757 bool DB::loadFile(DataList& _results, const std::string& _file) 758 { 759 const auto size = baseLib::filesystem::getFileSize(_file); 760 761 // unlikely that a 8mb file has useful data for us, skip 762 if (!size || size >= static_cast<size_t>(8 * 1024 * 1024)) 763 return false; 764 765 Data data; 766 if (!baseLib::filesystem::readFile(data, _file) || data.empty()) 767 return false; 768 769 return parseFileData(_results, data); 770 } 771 772 bool DB::loadLocalStorage(DataList& _results, const DataSource& _ds) 773 { 774 const auto file = getLocalStorageFile(_ds); 775 776 std::vector<uint8_t> data; 777 if (!baseLib::filesystem::readFile(data, file.getFullPathName().toStdString())) 778 return false; 779 780 synthLib::MidiToSysex::splitMultipleSysex(_results, data); 781 return !_results.empty(); 782 } 783 784 bool DB::loadFolder(const DataSourceNodePtr& _folder) 785 { 786 assert(_folder->type == SourceType::Folder); 787 788 std::vector<std::string> files; 789 baseLib::filesystem::findFiles(files, _folder->name, {}, 0, 0); 790 791 for (const auto& file : files) 792 { 793 const auto child = std::make_shared<DataSourceNode>(); 794 child->setParent(_folder); 795 child->name = file; 796 child->origin = DataSourceOrigin::Autogenerated; 797 798 if(baseLib::filesystem::isDirectory(file)) 799 child->type = SourceType::Folder; 800 else 801 child->type = SourceType::File; 802 803 addDataSource(child); 804 } 805 806 return !files.empty(); 807 } 808 809 bool DB::parseFileData(DataList& _results, const Data& _data) 810 { 811 return synthLib::MidiToSysex::extractSysexFromData(_results, _data); 812 } 813 814 void DB::startLoaderThread(const juce::File& _migrateFromDir/* = {}*/) 815 { 816 m_loader.start(); 817 818 runOnLoaderThread([this, _migrateFromDir] 819 { 820 if(!g_cacheEnabled || !loadCache()) 821 { 822 if(!loadJson()) 823 { 824 if(_migrateFromDir.isDirectory()) 825 { 826 m_settingsDir.createDirectory(); 827 828 std::vector<std::string> files; 829 baseLib::filesystem::getDirectoryEntries(files, _migrateFromDir.getFullPathName().toStdString()); 830 831 std::vector<juce::File> toBeDeleted; 832 833 for (const auto& file : files) 834 { 835 if(baseLib::filesystem::hasExtension(file, ".cache")) 836 { 837 juce::File f(file); 838 f.deleteFile(); 839 continue; 840 } 841 842 if(!baseLib::filesystem::hasExtension(file, ".json") && !baseLib::filesystem::hasExtension(file, ".syx")) 843 continue; 844 845 juce::File fileFrom(file); 846 juce::File fileTo(m_settingsDir.getChildFile(fileFrom.getFileName())); 847 848 if(!fileFrom.copyFileTo(fileTo)) 849 return; 850 851 toBeDeleted.push_back(fileFrom); 852 } 853 854 if(loadJson()) 855 { 856 for (auto& f : toBeDeleted) 857 f.deleteFile(); 858 } 859 } 860 } 861 } 862 }); 863 } 864 865 void DB::stopLoaderThread() 866 { 867 m_loader.destroy(); 868 } 869 870 void DB::runOnLoaderThread(std::function<void()>&& _func) 871 { 872 m_loader.add([this, f = std::move(_func)] 873 { 874 f(); 875 }); 876 } 877 878 void DB::runOnUiThread(const std::function<void()>& _func) 879 { 880 m_uiFuncs.push_back(_func); 881 } 882 883 void DB::addDataSource(const DataSourceNodePtr& _ds) 884 { 885 if (m_loader.destroyed()) 886 return; 887 888 auto ds = _ds; 889 890 bool dsExists; 891 892 { 893 std::unique_lock lockDs(m_dataSourcesMutex); 894 895 const auto itExisting = m_dataSources.find(*ds); 896 897 dsExists = itExisting != m_dataSources.end(); 898 899 if (dsExists) 900 { 901 // two things can happen here: 902 // * a child DS already exists and the one being added has a parent that was previously unknown to the existing DS 903 // * a DS is added again (for example manually requested by a user) even though it already exists because of a parent DS added earlier 904 905 ds = itExisting->second; 906 907 if(_ds->origin == DataSourceOrigin::Manual) 908 { 909 // user manually added a DS that already exists as a child 910 assert(!_ds->hasParent()); 911 ds->origin = _ds->origin; 912 } 913 else if(_ds->hasParent() && !ds->hasParent()) 914 { 915 // a parent datasource is added and this DS previously didn't have a parent 916 ds->setParent(_ds->getParent()); 917 } 918 else 919 { 920 // nothing to be done 921 assert(_ds->getParent().get() == ds->getParent().get()); 922 return; 923 } 924 925 std::unique_lock lockUi(m_uiMutex); 926 m_dirty.dataSources = true; 927 } 928 } 929 930 auto addDsToList = [&] 931 { 932 if (dsExists) 933 return; 934 935 std::unique_lock lockDs(m_dataSourcesMutex); 936 937 m_dataSources.insert({ *ds, ds }); 938 std::unique_lock lockUi(m_uiMutex); 939 m_dirty.dataSources = true; 940 941 dsExists = true; 942 }; 943 944 if (ds->type == SourceType::Folder) 945 { 946 addDsToList(); 947 loadFolder(ds); 948 return; 949 } 950 951 // always add DS if manually requested by user 952 if (ds->origin == DataSourceOrigin::Manual) 953 addDsToList(); 954 955 std::vector<std::vector<uint8_t>> data; 956 957 if(loadData(data, ds) && !data.empty()) 958 { 959 std::vector<PatchPtr> patches; 960 patches.reserve(data.size()); 961 962 const std::string defaultName = data.size() == 1 ? baseLib::filesystem::stripExtension(baseLib::filesystem::getFilenameWithoutPath(ds->name)) : ""; 963 964 for (uint32_t p = 0; p < data.size(); ++p) 965 { 966 if (const auto patch = initializePatch(std::move(data[p]), defaultName)) 967 { 968 patch->source = ds->weak_from_this(); 969 970 if(isValid(patch)) 971 { 972 patch->program = p; 973 patches.push_back(patch); 974 ds->patches.insert(patch); 975 } 976 } 977 } 978 979 if (!patches.empty()) 980 { 981 addDsToList(); 982 loadPatchModifications(ds, patches); 983 addPatches(patches); 984 } 985 } 986 } 987 988 bool DB::addPatches(const std::vector<PatchPtr>& _patches) 989 { 990 if (_patches.empty()) 991 return false; 992 993 std::unique_lock lock(m_patchesMutex); 994 995 for (const auto& patch : _patches) 996 { 997 const auto key = PatchKey(*patch); 998 999 // find modification and apply it to the patch 1000 const auto itMod = m_patchModifications.find(key); 1001 if (itMod != m_patchModifications.end()) 1002 { 1003 auto mods = itMod->second; 1004 assign(patch, mods); 1005 1006 m_patchModifications.erase(itMod); 1007 } 1008 1009 // add to all known categories, tags, etc 1010 for (const auto& it : patch->getTags().get()) 1011 { 1012 const auto type = it.first; 1013 const auto& tags = it.second; 1014 1015 for (const auto& tag : tags.getAdded()) 1016 internalAddTag(type, tag); 1017 } 1018 } 1019 1020 // add to ongoing searches 1021 updateSearches(_patches); 1022 1023 return true; 1024 } 1025 1026 bool DB::removePatch(const PatchPtr& _patch) 1027 { 1028 std::unique_lock lock(m_patchesMutex); 1029 1030 const auto itDs = m_dataSources.find(*_patch->source.lock()); 1031 1032 if(itDs == m_dataSources.end()) 1033 return false; 1034 1035 const auto& ds = itDs->second; 1036 auto& patches = ds->patches; 1037 1038 const auto it = patches.find(_patch); 1039 if (it == patches.end()) 1040 return false; 1041 1042 preservePatchModifications(_patch); 1043 1044 patches.erase(it); 1045 1046 removePatchesFromSearches({ _patch }); 1047 1048 std::unique_lock lockUi(m_uiMutex); 1049 m_dirty.patches = true; 1050 1051 return true; 1052 } 1053 1054 bool DB::internalAddTag(TagType _type, const Tag& _tag) 1055 { 1056 const auto itType = m_tags.find(_type); 1057 1058 if (itType == m_tags.end()) 1059 { 1060 m_tags.insert({ _type, {_tag} }); 1061 1062 std::unique_lock lockUi(m_uiMutex); 1063 m_dirty.tags.insert(_type); 1064 return true; 1065 } 1066 1067 auto& tags = itType->second; 1068 1069 if (tags.find(_tag) != tags.end()) 1070 return false; 1071 1072 tags.insert(_tag); 1073 std::unique_lock lockUi(m_uiMutex); 1074 m_dirty.tags.insert(_type); 1075 1076 return true; 1077 } 1078 1079 bool DB::internalRemoveTag(const TagType _type, const Tag& _tag) 1080 { 1081 const auto& itType = m_tags.find(_type); 1082 1083 if (itType == m_tags.end()) 1084 return false; 1085 1086 auto& tags = itType->second; 1087 const auto itTag = tags.find(_tag); 1088 1089 if (itTag == tags.end()) 1090 return false; 1091 1092 tags.erase(itTag); 1093 1094 std::unique_lock lockUi(m_uiMutex); 1095 m_dirty.tags.insert(_type); 1096 1097 return true; 1098 } 1099 1100 bool DB::executeSearch(Search& _search) 1101 { 1102 _search.state = SearchState::Running; 1103 1104 const auto reqPatch = _search.request.patch; 1105 if(reqPatch) 1106 { 1107 // we're searching by patch content to find patches within datasources 1108 SearchResult results; 1109 1110 std::shared_lock lockDs(m_dataSourcesMutex); 1111 1112 for (const auto& [_, ds] : m_dataSources) 1113 { 1114 for (const auto& patch : ds->patches) 1115 { 1116 if(patch->hash == reqPatch->hash) 1117 results.insert(patch); 1118 else if(patch->sysex.size() == reqPatch->sysex.size() && patch->getName() == reqPatch->getName()) 1119 { 1120 // if patches are not 100% identical, they might still be the same patch as unknown/unused data in dumps might have different values 1121 if(equals(patch, reqPatch)) 1122 results.insert(patch); 1123 } 1124 } 1125 } 1126 1127 if(!results.empty()) 1128 { 1129 std::unique_lock searchLock(_search.resultsMutex); 1130 std::swap(_search.results, results); 1131 } 1132 1133 _search.setCompleted(); 1134 1135 std::unique_lock lockUi(m_uiMutex); 1136 m_dirty.searches.insert(_search.handle); 1137 return true; 1138 } 1139 1140 auto searchInDs = [&](const DataSourceNodePtr& _ds) 1141 { 1142 if(!_search.request.sourceNode && _search.getSourceType() != SourceType::Invalid) 1143 { 1144 if(_ds->type != _search.request.sourceType) 1145 return true; 1146 } 1147 1148 bool isCancelled; 1149 { 1150 std::shared_lock lockSearches(m_searchesMutex); 1151 const auto it = m_cancelledSearches.find(_search.handle); 1152 isCancelled = it != m_cancelledSearches.end(); 1153 if(isCancelled) 1154 m_cancelledSearches.erase(it); 1155 } 1156 1157 if(isCancelled) 1158 { 1159 _search.state = SearchState::Cancelled; 1160 std::unique_lock lockUi(m_uiMutex); 1161 m_dirty.searches.insert(_search.handle); 1162 return false; 1163 } 1164 1165 for (const auto& patchPtr : _ds->patches) 1166 { 1167 const auto* patch = patchPtr.get(); 1168 assert(patch); 1169 1170 if(_search.request.match(*patch)) 1171 { 1172 std::unique_lock searchLock(_search.resultsMutex); 1173 _search.results.insert(patchPtr); 1174 } 1175 } 1176 return true; 1177 }; 1178 1179 if(_search.request.sourceNode && (_search.getSourceType() == SourceType::File || _search.getSourceType() == SourceType::LocalStorage)) 1180 { 1181 const auto& it = m_dataSources.find(*_search.request.sourceNode); 1182 1183 if(it == m_dataSources.end()) 1184 { 1185 _search.setCompleted(); 1186 return false; 1187 } 1188 1189 if(!searchInDs(it->second)) 1190 return false; 1191 } 1192 else 1193 { 1194 for (const auto& it : m_dataSources) 1195 { 1196 if(!searchInDs(it.second)) 1197 return false; 1198 } 1199 } 1200 1201 _search.setCompleted(); 1202 1203 { 1204 std::unique_lock lockUi(m_uiMutex); 1205 m_dirty.searches.insert(_search.handle); 1206 } 1207 1208 return true; 1209 } 1210 1211 void DB::updateSearches(const std::vector<PatchPtr>& _patches) 1212 { 1213 std::shared_lock lockSearches(m_searchesMutex); 1214 1215 std::set<SearchHandle> dirtySearches; 1216 1217 for (const auto& it : m_searches) 1218 { 1219 const auto handle = it.first; 1220 auto& search = it.second; 1221 1222 bool searchDirty = false; 1223 1224 for (const auto& patch : _patches) 1225 { 1226 const auto match = search->request.match(*patch); 1227 1228 bool countChanged; 1229 1230 { 1231 std::unique_lock lock(search->resultsMutex); 1232 const auto oldCount = search->results.size(); 1233 1234 if (match) 1235 search->results.insert(patch); 1236 else 1237 search->results.erase(patch); 1238 1239 const auto newCount = search->results.size(); 1240 countChanged = newCount != oldCount; 1241 } 1242 1243 if (countChanged) 1244 { 1245 searchDirty = true; 1246 } 1247 else 1248 { 1249 // this type of search is used to indicate that a patch has changed its properties, mark as dirty in this case too 1250 if(search->request.patch == patch) 1251 searchDirty = true; 1252 } 1253 } 1254 if (searchDirty) 1255 dirtySearches.insert(handle); 1256 } 1257 1258 if (dirtySearches.empty()) 1259 return; 1260 1261 std::unique_lock lockUi(m_uiMutex); 1262 1263 for (SearchHandle dirtySearch : dirtySearches) 1264 m_dirty.searches.insert(dirtySearch); 1265 } 1266 1267 bool DB::removePatchesFromSearches(const std::vector<PatchPtr>& _keys) 1268 { 1269 bool res = false; 1270 1271 std::shared_lock lockSearches(m_searchesMutex); 1272 1273 for (auto& itSearches : m_searches) 1274 { 1275 const auto& search = itSearches.second; 1276 1277 bool countChanged; 1278 { 1279 std::unique_lock lockResults(search->resultsMutex); 1280 const auto oldCount = search->results.size(); 1281 1282 for (const auto& key : _keys) 1283 search->results.erase(key); 1284 1285 const auto newCount = search->results.size(); 1286 countChanged = newCount != oldCount; 1287 } 1288 1289 if (countChanged) 1290 { 1291 res = true; 1292 std::unique_lock lockUi(m_uiMutex); 1293 m_dirty.searches.insert(itSearches.first); 1294 } 1295 } 1296 return res; 1297 } 1298 1299 void DB::preservePatchModifications(const PatchPtr& _patch) 1300 { 1301 auto mods = _patch->modifications; 1302 1303 if(!mods || mods->empty()) 1304 return; 1305 1306 mods->patch.reset(); 1307 mods->updateCache(); 1308 1309 m_patchModifications.insert({PatchKey(*_patch), mods}); 1310 } 1311 1312 void DB::preservePatchModifications(const std::vector<PatchPtr>& _patches) 1313 { 1314 for (const auto& patch : _patches) 1315 preservePatchModifications(patch); 1316 } 1317 1318 bool DB::createConsecutiveProgramNumbers(const DataSourceNodePtr& _ds) const 1319 { 1320 std::unique_lock lockPatches(m_patchesMutex); 1321 return _ds->createConsecutiveProgramNumbers(); 1322 } 1323 1324 Color DB::getTagColorInternal(const TagType _type, const Tag& _tag) const 1325 { 1326 const auto itType = m_tagColors.find(_type); 1327 if(itType == m_tagColors.end()) 1328 return 0; 1329 const auto itTag = itType->second.find(_tag); 1330 if(itTag == itType->second.end()) 1331 return 0; 1332 return itTag->second; 1333 } 1334 1335 bool DB::loadJson() 1336 { 1337 bool success = true; 1338 1339 const auto jsonFile = getJsonFile(); 1340 1341 if(!jsonFile.existsAsFile()) 1342 return false; 1343 1344 const auto json = juce::JSON::parse(jsonFile); 1345 const auto* datasources = json["datasources"].getArray(); 1346 1347 if(datasources) 1348 { 1349 for(int i=0; i<datasources->size(); ++i) 1350 { 1351 const auto var = datasources->getUnchecked(i); 1352 1353 DataSource ds; 1354 1355 ds.type = toSourceType(var["type"].toString().toStdString()); 1356 ds.name = var["name"].toString().toStdString(); 1357 ds.origin = DataSourceOrigin::Manual; 1358 1359 if (ds.type != SourceType::Invalid && !ds.name.empty()) 1360 { 1361 addDataSource(ds, false); 1362 } 1363 else 1364 { 1365 LOG("Unexpected data source type " << toString(ds.type) << " with name '" << ds.name << "'"); 1366 success = false; 1367 } 1368 } 1369 } 1370 1371 { 1372 std::unique_lock lockPatches(m_patchesMutex); 1373 1374 if(auto* tags = json["tags"].getDynamicObject()) 1375 { 1376 const auto& props = tags->getProperties(); 1377 for (const auto& it : props) 1378 { 1379 const auto strType = it.name.toString().toStdString(); 1380 const auto type = toTagType(strType); 1381 1382 const auto* tagsArray = it.value.getArray(); 1383 if(tagsArray) 1384 { 1385 std::set<Tag> newTags; 1386 for(int i=0; i<tagsArray->size(); ++i) 1387 { 1388 const auto tag = tagsArray->getUnchecked(i).toString().toStdString(); 1389 newTags.insert(tag); 1390 } 1391 m_tags.insert({ type, newTags }); 1392 m_dirty.tags.insert(type); 1393 } 1394 else 1395 { 1396 LOG("Unexpected empty tags for tag type " << strType); 1397 success = false; 1398 } 1399 } 1400 } 1401 1402 if(auto* tagColors = json["tagColors"].getDynamicObject()) 1403 { 1404 const auto& props = tagColors->getProperties(); 1405 1406 for (const auto& it : props) 1407 { 1408 const auto strType = it.name.toString().toStdString(); 1409 const auto type = toTagType(strType); 1410 1411 auto* colors = it.value.getDynamicObject(); 1412 if(colors) 1413 { 1414 std::unordered_map<Tag, Color> newTags; 1415 for (const auto& itCol : colors->getProperties()) 1416 { 1417 const auto tag = itCol.name.toString().toStdString(); 1418 const auto col = static_cast<juce::int64>(itCol.value); 1419 if(!tag.empty() && col != g_invalidColor && col >= std::numeric_limits<Color>::min() && col <= std::numeric_limits<Color>::max()) 1420 newTags[tag] = static_cast<Color>(col); 1421 } 1422 m_tagColors[type] = newTags; 1423 m_dirty.tags.insert(type); 1424 } 1425 else 1426 { 1427 LOG("Unexpected empty tags for tag type " << strType); 1428 success = false; 1429 } 1430 } 1431 } 1432 1433 if(!loadPatchModifications(m_patchModifications, json)) 1434 success = false; 1435 } 1436 1437 return success; 1438 } 1439 1440 bool DB::loadPatchModifications(const DataSourceNodePtr& _ds, const std::vector<PatchPtr>& _patches) 1441 { 1442 if(_patches.empty()) 1443 return true; 1444 1445 const auto file = getJsonFile(*_ds); 1446 if(file.getFileName().isEmpty()) 1447 return false; 1448 1449 if(!file.exists()) 1450 return true; 1451 1452 const auto json = juce::JSON::parse(file); 1453 1454 std::map<PatchKey, PatchModificationsPtr> patchModifications; 1455 1456 if(!loadPatchModifications(patchModifications, json, _ds)) 1457 return false; 1458 1459 // apply modifications to patches 1460 for (const auto& patch : _patches) 1461 { 1462 const auto key = PatchKey(*patch); 1463 const auto it = patchModifications.find(key); 1464 if(it != patchModifications.end()) 1465 { 1466 assign(patch, it->second); 1467 patchModifications.erase(it); 1468 1469 if(patchModifications.empty()) 1470 break; 1471 } 1472 } 1473 1474 if(!patchModifications.empty()) 1475 { 1476 // any patch modification that we couldn't apply to a patch is added to the global modifications 1477 for (const auto& patchModification : patchModifications) 1478 m_patchModifications.insert(patchModification); 1479 } 1480 1481 return true; 1482 } 1483 1484 bool DB::loadPatchModifications(std::map<PatchKey, PatchModificationsPtr>& _patchModifications, const juce::var& _parentNode, const DataSourceNodePtr& _dataSource/* = nullptr*/) 1485 { 1486 auto* patches = _parentNode["patches"].getDynamicObject(); 1487 if(!patches) 1488 return true; 1489 1490 bool success = true; 1491 1492 const auto& props = patches->getProperties(); 1493 for (const auto& it : props) 1494 { 1495 const auto strKey = it.name.toString().toStdString(); 1496 const auto var = it.value; 1497 1498 auto key = PatchKey::fromString(strKey, _dataSource); 1499 1500 auto mods = std::make_shared<PatchModifications>(); 1501 1502 if (!mods->deserialize(var)) 1503 { 1504 LOG("Failed to parse patch modifications for key " << strKey); 1505 success = false; 1506 continue; 1507 } 1508 1509 if(!key.isValid()) 1510 { 1511 LOG("Failed to parse patch key from string " << strKey); 1512 success = false; 1513 } 1514 1515 _patchModifications.insert({ key, mods }); 1516 } 1517 1518 return success; 1519 } 1520 1521 bool DB::deleteFile(const juce::File& _file) 1522 { 1523 if (!_file.existsAsFile()) 1524 return true; 1525 if (_file.deleteFile()) 1526 return true; 1527 1528 pushError("Failed to delete file:\n" + _file.getFullPathName().toStdString()); 1529 return false; 1530 } 1531 1532 bool DB::saveJson() 1533 { 1534 m_cacheDirty = true; 1535 1536 const auto cacheFile = getCacheFile(); 1537 const auto jsonFile = getJsonFile(); 1538 1539 jsonFile.createDirectory(); 1540 1541 deleteFile(cacheFile); 1542 1543 if (!jsonFile.hasWriteAccess()) 1544 { 1545 pushError("No write access to file:\n" + jsonFile.getFullPathName().toStdString()); 1546 return false; 1547 } 1548 1549 auto* json = new juce::DynamicObject(); 1550 1551 { 1552 std::shared_lock lockDs(m_dataSourcesMutex); 1553 std::shared_lock lockP(m_patchesMutex); 1554 1555 auto patchModifications = m_patchModifications; 1556 1557 juce::Array<juce::var> dss; 1558 1559 for (const auto& it : m_dataSources) 1560 { 1561 const auto& dataSource = it.second; 1562 1563 // if we cannot save patch modifications to a separate file, add them to the global file 1564 if(!saveJson(dataSource)) 1565 { 1566 #ifdef _DEBUG 1567 assert(dataSource->type != SourceType::LocalStorage && "expected separate json for local storage, unable to write file"); 1568 #endif 1569 for (const auto& patch : dataSource->patches) 1570 { 1571 if(!patch->modifications || patch->modifications->empty()) 1572 continue; 1573 1574 patchModifications.insert({PatchKey(*patch), patch->modifications}); 1575 } 1576 } 1577 if (dataSource->origin != DataSourceOrigin::Manual) 1578 continue; 1579 1580 if (dataSource->type == SourceType::Rom) 1581 continue; 1582 1583 auto* o = new juce::DynamicObject(); 1584 1585 o->setProperty("type", juce::String(toString(dataSource->type))); 1586 o->setProperty("name", juce::String(dataSource->name)); 1587 1588 dss.add(o); 1589 } 1590 json->setProperty("datasources", dss); 1591 1592 saveLocalStorage(); 1593 1594 auto* tagTypes = new juce::DynamicObject(); 1595 1596 for (const auto& it : m_tags) 1597 { 1598 const auto type = it.first; 1599 const auto& tags = it.second; 1600 1601 if(tags.empty()) 1602 continue; 1603 1604 juce::Array<juce::var> tagsArray; 1605 for (const auto& tag : tags) 1606 tagsArray.add(juce::String(tag)); 1607 1608 tagTypes->setProperty(juce::String(toString(type)), tagsArray); 1609 } 1610 1611 json->setProperty("tags", tagTypes); 1612 1613 auto* tagColors = new juce::DynamicObject(); 1614 1615 for (const auto& it : m_tagColors) 1616 { 1617 const auto type = it.first; 1618 const auto& tags = it.second; 1619 1620 if(tags.empty()) 1621 continue; 1622 1623 auto* colors = new juce::DynamicObject(); 1624 for (const auto& [tag, col] : tags) 1625 colors->setProperty(juce::String(tag), static_cast<juce::int64>(col)); 1626 1627 tagColors->setProperty(juce::String(toString(type)), colors); 1628 } 1629 1630 json->setProperty("tagColors", tagColors); 1631 1632 auto* patchMods = new juce::DynamicObject(); 1633 1634 for (const auto& it : patchModifications) 1635 { 1636 const auto& key = it.first; 1637 const auto& mods = it.second; 1638 1639 if (mods->empty()) 1640 continue; 1641 1642 auto* obj = mods->serialize(); 1643 1644 patchMods->setProperty(juce::String(key.toString()), obj); 1645 } 1646 1647 json->setProperty("patches", patchMods); 1648 } 1649 1650 return saveJson(jsonFile, json); 1651 } 1652 1653 juce::File DB::getJsonFile(const DataSource& _ds) const 1654 { 1655 if(_ds.type == SourceType::LocalStorage) 1656 return {getLocalStorageFile(_ds).getFullPathName() + ".json"}; 1657 if(_ds.type == SourceType::File) 1658 return {_ds.name + ".json"}; 1659 return {}; 1660 } 1661 1662 bool DB::saveJson(const DataSourceNodePtr& _ds) 1663 { 1664 if(!_ds) 1665 return false; 1666 1667 auto filename = getJsonFile(*_ds); 1668 1669 if(filename.getFileName().isEmpty()) 1670 return _ds->patches.empty(); 1671 1672 if(!juce::File::isAbsolutePath(filename.getFullPathName())) 1673 filename = m_settingsDir.getChildFile(filename.getFullPathName()); 1674 1675 if(_ds->patches.empty()) 1676 { 1677 filename.deleteFile(); 1678 return true; 1679 } 1680 1681 juce::DynamicObject* patchMods = nullptr; 1682 1683 for (const auto& patch : _ds->patches) 1684 { 1685 const auto mods = patch->modifications; 1686 1687 if(!mods || mods->empty()) 1688 continue; 1689 1690 auto* obj = mods->serialize(); 1691 1692 if(!patchMods) 1693 patchMods = new juce::DynamicObject(); 1694 1695 const auto key = PatchKey(*patch); 1696 1697 patchMods->setProperty(juce::String(key.toString(false)), obj); 1698 } 1699 1700 if(!patchMods) 1701 { 1702 filename.deleteFile(); 1703 return true; 1704 } 1705 1706 auto* json = new juce::DynamicObject(); 1707 1708 json->setProperty("patches", patchMods); 1709 1710 return saveJson(filename, json); 1711 } 1712 1713 bool DB::saveJson(const juce::File& _target, juce::DynamicObject* _src) 1714 { 1715 if (!_target.hasWriteAccess()) 1716 { 1717 pushError("No write access to file:\n" + _target.getFullPathName().toStdString()); 1718 return false; 1719 } 1720 const auto tempFile = juce::File(_target.getFullPathName() + "_tmp.json"); 1721 if (!tempFile.hasWriteAccess()) 1722 { 1723 pushError("No write access to file:\n" + tempFile.getFullPathName().toStdString()); 1724 return false; 1725 } 1726 const auto jsonText = juce::JSON::toString(juce::var(_src), false); 1727 if (!tempFile.replaceWithText(jsonText)) 1728 { 1729 pushError("Failed to write data to file:\n" + tempFile.getFullPathName().toStdString()); 1730 return false; 1731 } 1732 if (!tempFile.copyFileTo(_target)) 1733 { 1734 pushError("Failed to copy\n" + tempFile.getFullPathName().toStdString() + "\nto\n" + _target.getFullPathName().toStdString()); 1735 return false; 1736 } 1737 deleteFile(tempFile); 1738 return true; 1739 } 1740 1741 juce::File DB::getLocalStorageFile(const DataSource& _ds) const 1742 { 1743 const auto filename = createValidFilename(_ds.name); 1744 1745 return m_settingsDir.getChildFile(filename + ".syx"); 1746 } 1747 1748 bool DB::saveLocalStorage() 1749 { 1750 std::map<DataSourceNodePtr, std::set<PatchPtr>> localStoragePatches; 1751 1752 for (const auto& it : m_dataSources) 1753 { 1754 const auto& ds = it.second; 1755 1756 if (ds->type == SourceType::LocalStorage) 1757 localStoragePatches.insert({ds, ds->patches}); 1758 } 1759 1760 if (localStoragePatches.empty()) 1761 return false; 1762 1763 std::vector<PatchPtr> patchesVec; 1764 patchesVec.reserve(128); 1765 1766 bool res = true; 1767 1768 for (const auto& it : localStoragePatches) 1769 { 1770 const auto& ds = it.first; 1771 const auto& patches = it.second; 1772 1773 const auto file = getLocalStorageFile(*ds); 1774 1775 if(patches.empty()) 1776 { 1777 deleteFile(file); 1778 } 1779 else 1780 { 1781 patchesVec.assign(patches.begin(), patches.end()); 1782 DataSource::sortByProgram(patchesVec); 1783 if(!writePatchesToFile(file, patchesVec)) 1784 res = false; 1785 } 1786 } 1787 return res; 1788 } 1789 1790 void DB::pushError(std::string _string) 1791 { 1792 std::unique_lock lockUi(m_uiMutex); 1793 m_dirty.errors.emplace_back(std::move(_string)); 1794 } 1795 1796 bool DB::loadCache() 1797 { 1798 m_cacheDirty = true; 1799 1800 const auto cacheFile = getCacheFile(); 1801 1802 if(!cacheFile.existsAsFile()) 1803 return false; 1804 1805 std::vector<uint8_t> data; 1806 if(!baseLib::filesystem::readFile(data, cacheFile.getFullPathName().toStdString())) 1807 return false; 1808 1809 try 1810 { 1811 baseLib::BinaryStream inStream(data); 1812 1813 auto stream = inStream.tryReadChunk(chunks::g_patchManager, chunkVersions::g_patchManager); 1814 1815 if(!stream) 1816 return false; 1817 1818 std::unique_lock lockDS(m_dataSourcesMutex); 1819 std::unique_lock lockP(m_patchesMutex); 1820 1821 std::map<DataSource, DataSourceNodePtr> resultDataSources; 1822 std::unordered_map<TagType, std::set<Tag>> resultTags; 1823 std::unordered_map<TagType, std::unordered_map<Tag, uint32_t>> resultTagColors; 1824 std::map<PatchKey, PatchModificationsPtr> resultPatchModifications; 1825 1826 if(auto s = stream.tryReadChunk(chunks::g_patchManagerDataSources, 1)) 1827 { 1828 const auto numDatasources = s.read<uint32_t>(); 1829 1830 std::vector<DataSource> dataSources; 1831 dataSources.reserve(numDatasources); 1832 1833 for(uint32_t i=0; i<numDatasources; ++i) 1834 { 1835 DataSource& ds = dataSources.emplace_back(); 1836 if(!ds.read(s)) 1837 return false; 1838 } 1839 1840 // read tree information 1841 std::map<uint32_t, uint32_t> childToParentMap; 1842 const auto childToParentCount = s.read<uint32_t>(); 1843 for(uint32_t i=0; i<childToParentCount; ++i) 1844 { 1845 const auto child = s.read<uint32_t>(); 1846 const auto parent = s.read<uint32_t>(); 1847 childToParentMap.insert({child, parent}); 1848 } 1849 1850 std::vector<DataSourceNodePtr> nodes; 1851 nodes.resize(dataSources.size()); 1852 1853 // create root nodes first 1854 for(uint32_t i=0; i<dataSources.size(); ++i) 1855 { 1856 if(childToParentMap.find(i) != childToParentMap.end()) 1857 continue; 1858 1859 const auto& ds = dataSources[i]; 1860 const auto node = std::make_shared<DataSourceNode>(ds); 1861 nodes[i] = node; 1862 resultDataSources.insert({ds, node}); 1863 } 1864 1865 // now iteratively create children until there are none left 1866 while(!childToParentMap.empty()) 1867 { 1868 for(auto it = childToParentMap.begin(); it != childToParentMap.end();) 1869 { 1870 const auto childId = it->first; 1871 const auto parentId = it->second; 1872 1873 const auto& parent = nodes[parentId]; 1874 if(!parent) 1875 { 1876 ++it; 1877 continue; 1878 } 1879 1880 const auto& ds = dataSources[childId]; 1881 const auto node = std::make_shared<DataSourceNode>(ds); 1882 node->setParent(parent); 1883 nodes[childId] = node; 1884 resultDataSources.insert({ds, node}); 1885 1886 it = childToParentMap.erase(it); 1887 } 1888 } 1889 1890 // now as we have the datasources created as nodes, patches need to know the datasource nodes they are part of 1891 for (const auto& it : resultDataSources) 1892 { 1893 for(auto& patch : it.second->patches) 1894 patch->source = it.second->weak_from_this(); 1895 } 1896 } 1897 else 1898 return false; 1899 1900 if(auto s = stream.tryReadChunk(chunks::g_patchManagerTags, 1)) 1901 { 1902 const auto tagTypeCount = s.read<uint32_t>(); 1903 1904 for(uint32_t i=0; i<tagTypeCount; ++i) 1905 { 1906 const auto tagType = static_cast<TagType>(s.read<uint8_t>()); 1907 const auto tagCount = s.read<uint32_t>(); 1908 1909 std::set<Tag> tags; 1910 for(uint32_t t=0; t<tagCount; ++t) 1911 tags.insert(s.readString()); 1912 1913 resultTags.insert({tagType, tags}); 1914 } 1915 } 1916 else 1917 return false; 1918 1919 if(auto s = stream.tryReadChunk(chunks::g_patchManagerTagColors, 1)) 1920 { 1921 const auto tagTypeCount = s.read<uint32_t>(); 1922 1923 for(uint32_t i=0; i<tagTypeCount; ++i) 1924 { 1925 const auto tagType = static_cast<TagType>(s.read<uint8_t>()); 1926 std::unordered_map<Tag, Color> tagToColor; 1927 1928 const auto count = s.read<uint32_t>(); 1929 for(uint32_t c=0; c<count; ++c) 1930 { 1931 const auto tag = s.readString(); 1932 const auto color = s.read<Color>(); 1933 tagToColor.insert({tag, color}); 1934 } 1935 resultTagColors.insert({tagType, tagToColor}); 1936 } 1937 } 1938 else 1939 return false; 1940 1941 if(auto s = stream.tryReadChunk(chunks::g_patchManagerPatchModifications, 1)) 1942 { 1943 const auto count = s.read<uint32_t>(); 1944 1945 for(uint32_t i=0; i<count; ++i) 1946 { 1947 auto key = PatchKey::fromString(s.readString()); 1948 1949 const auto itDS = resultDataSources.find(*key.source); 1950 if(itDS != resultDataSources.end()) 1951 key.source = itDS->second; 1952 1953 auto mods = std::make_shared<PatchModifications>(); 1954 if(!mods->read(s)) 1955 return false; 1956 1957 resultPatchModifications.insert({key, mods}); 1958 1959 for (const auto& it : resultDataSources) 1960 { 1961 for (const auto& patch : it.second->patches) 1962 { 1963 if(*patch != key) 1964 continue; 1965 1966 assign(patch, mods); 1967 } 1968 } 1969 } 1970 } 1971 else 1972 return false; 1973 1974 m_dataSources = resultDataSources; 1975 m_tags = resultTags; 1976 m_tagColors = resultTagColors; 1977 m_patchModifications = resultPatchModifications; 1978 1979 for (const auto& it: resultDataSources) 1980 { 1981 const auto& patches = it.second->patches; 1982 updateSearches({patches.begin(), patches.end()}); 1983 } 1984 1985 { 1986 std::unique_lock lockUi(m_uiMutex); 1987 1988 m_dirty.dataSources = true; 1989 m_dirty.patches = true; 1990 1991 for (const auto& it : m_tags) 1992 m_dirty.tags.insert(it.first); 1993 } 1994 1995 m_cacheDirty = false; 1996 1997 return true; 1998 } 1999 catch(const std::range_error& e) 2000 { 2001 LOG("Failed to read patch manager cache, " << e.what()); 2002 return false; 2003 } 2004 } 2005 2006 void DB::saveCache() 2007 { 2008 const auto cacheFile = getCacheFile(); 2009 2010 if(!cacheFile.hasWriteAccess()) 2011 return; 2012 2013 baseLib::BinaryStream outStream; 2014 { 2015 std::shared_lock lockDS(m_dataSourcesMutex); 2016 std::shared_lock lockP(m_patchesMutex); 2017 2018 baseLib::ChunkWriter cw(outStream, chunks::g_patchManager, chunkVersions::g_patchManager); 2019 { 2020 baseLib::ChunkWriter cwDS(outStream, chunks::g_patchManagerDataSources, 1); 2021 2022 outStream.write<uint32_t>(static_cast<uint32_t>(m_dataSources.size())); 2023 2024 // create an id map to save space when writing child->parent dependencies 2025 std::map<DataSource, uint32_t> idMap; 2026 uint32_t index = 0; 2027 2028 // write datasources and create id map 2029 for (const auto& it : m_dataSources) 2030 { 2031 idMap.insert({it.first, index++}); 2032 2033 it.second->write(outStream); 2034 } 2035 2036 // create child->parent map 2037 std::map<uint32_t, uint32_t> childToParent; 2038 2039 for (const auto& it : m_dataSources) 2040 { 2041 const auto& childDS = it.second; 2042 const auto& parentDS = childDS->getParent(); 2043 2044 if(parentDS) 2045 { 2046 const auto idChild = idMap.find(*childDS)->second; 2047 const auto idParent = idMap.find(*parentDS)->second; 2048 2049 childToParent.insert({idChild, idParent}); 2050 } 2051 } 2052 2053 // write child->parent dependency tree 2054 outStream.write<uint32_t>(static_cast<uint32_t>(childToParent.size())); 2055 2056 for (const auto& it : childToParent) 2057 { 2058 outStream.write(it.first); 2059 outStream.write(it.second); 2060 } 2061 } 2062 2063 { 2064 // write tags 2065 baseLib::ChunkWriter cwDS(outStream, chunks::g_patchManagerTags, 1); 2066 2067 outStream.write(static_cast<uint32_t>(m_tags.size())); 2068 2069 for (const auto& it : m_tags) 2070 { 2071 const auto tagType = it.first; 2072 const auto& tags = it.second; 2073 2074 outStream.write(static_cast<uint8_t>(tagType)); 2075 outStream.write(static_cast<uint32_t>(tags.size())); 2076 2077 for (const auto& tag : tags) 2078 outStream.write(tag); 2079 } 2080 } 2081 2082 { 2083 // write tag colors 2084 baseLib::ChunkWriter cwDS(outStream, chunks::g_patchManagerTagColors, 1); 2085 2086 outStream.write(static_cast<uint32_t>(m_tagColors.size())); 2087 2088 for (const auto& it : m_tagColors) 2089 { 2090 const auto tagType = it.first; 2091 const auto& mapStringToColor = it.second; 2092 2093 outStream.write(static_cast<uint8_t>(tagType)); 2094 outStream.write(static_cast<uint32_t>(mapStringToColor.size())); 2095 2096 for (const auto& itStringToColor : mapStringToColor) 2097 { 2098 outStream.write(itStringToColor.first); 2099 outStream.write(itStringToColor.second); 2100 } 2101 } 2102 } 2103 2104 { 2105 // write patch modifications 2106 baseLib::ChunkWriter cwDS(outStream, chunks::g_patchManagerPatchModifications, 1); 2107 2108 outStream.write(static_cast<uint32_t>(m_patchModifications.size())); 2109 2110 for (const auto& it : m_patchModifications) 2111 { 2112 const auto key = it.first; 2113 const auto mods = it.second; 2114 2115 outStream.write(key.toString(true)); 2116 mods->write(outStream); 2117 } 2118 } 2119 } 2120 2121 std::vector<uint8_t> buffer; 2122 outStream.toVector(buffer); 2123 2124 cacheFile.replaceWithData(buffer.data(), buffer.size()); 2125 2126 m_cacheDirty = false; 2127 } 2128 2129 juce::File DB::getCacheFile() const 2130 { 2131 return m_settingsDir.getChildFile(g_fileNameCache); 2132 } 2133 2134 juce::File DB::getJsonFile() const 2135 { 2136 return m_settingsDir.getChildFile(g_filenameJson); 2137 } 2138 }