gearmulator

Emulation of classic VA synths of the late 90s/2000s that are based on Motorola 56300 family DSPs
Log | Files | Refs | Submodules | README | LICENSE

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 }