#include "AudioGroupFilePresenter.hpp" #include #include #import "AmuseContainingApp.hpp" #import "AudioUnitBackend.hpp" #import "AudioUnitViewController.hpp" static std::string StrToLower(const std::string& str) { std::string ret = str; std::transform(ret.begin(), ret.end(), ret.begin(), tolower); return ret; } @implementation AudioGroupDataToken - (id)initWithDataCollection:(AudioGroupDataCollection *)collection { self = [super init]; m_collection = collection; return self; } @end @implementation AudioGroupCollectionToken - (id)initWithCollection:(AudioGroupCollection *)collection { self = [super init]; m_collection = collection; return self; } @end @implementation AudioGroupSFXToken - (id)initWithName:(NSAttributedString*)name loadId:(int)loadId sfx:(const amuse::SFXGroupIndex::SFXEntry*)sfx { self = [super init]; m_name = name; m_loadId = loadId; m_sfx = sfx; return self; } @end @implementation AudioGroupSampleToken - (id)initWithName:(NSAttributedString*)name samp:(const std::pair*)sample { self = [super init]; m_name = name; m_sample = sample; return self; } @end @implementation AudioGroupToken - (id)initWithName:(NSString*)name id:(int)gid songGroup:(const amuse::SongGroupIndex*)group { self = [super init]; m_name = name; m_song = group; m_id = gid; return self; } - (id)initWithName:(NSString*)name id:(int)gid sfxGroup:(const amuse::SFXGroupIndex*)group { self = [super init]; m_name = name; m_sfx = group; m_id = gid; return self; } @end @implementation AudioGroupFilePresenter - (NSURL*)presentedItemURL { return m_groupURL; } - (NSOperationQueue*)presentedItemOperationQueue { return [NSOperationQueue mainQueue]; } AudioGroupCollection::AudioGroupCollection(NSURL* url) : m_url(url), m_token([[AudioGroupCollectionToken alloc] initWithCollection:this]) {} void AudioGroupCollection::addCollection(AudioGroupFilePresenter* presenter, std::vector>&& collection) { for (std::pair& pair : collection) { NSURL* collectionUrl = [m_url URLByAppendingPathComponent:@(pair.first.c_str())]; amuse::IntrusiveAudioGroupData& dataIn = pair.second; auto search = m_groups.find(pair.first); if (search == m_groups.end()) { search = m_groups.emplace(pair.first, std::make_unique(pair.first, [collectionUrl URLByAppendingPathComponent:@"proj"], [collectionUrl URLByAppendingPathComponent:@"pool"], [collectionUrl URLByAppendingPathComponent:@"sdir"], [collectionUrl URLByAppendingPathComponent:@"samp"], [collectionUrl URLByAppendingPathComponent:@"meta"])).first; } AudioGroupDataCollection& dataCollection = *search->second; dataCollection.m_projData.resize(dataIn.getProjSize()); memmove(dataCollection.m_projData.data(), dataIn.getProj(), dataIn.getProjSize()); dataCollection.m_poolData.resize(dataIn.getPoolSize()); memmove(dataCollection.m_poolData.data(), dataIn.getPool(), dataIn.getPoolSize()); dataCollection.m_sdirData.resize(dataIn.getSdirSize()); memmove(dataCollection.m_sdirData.data(), dataIn.getSdir(), dataIn.getSdirSize()); dataCollection.m_sampData.resize(dataIn.getSampSize()); memmove(dataCollection.m_sampData.data(), dataIn.getSamp(), dataIn.getSampSize()); dataCollection.m_metaData.emplace(dataIn.getDataFormat(), dataIn.getAbsoluteProjOffsets(), true); dataCollection._indexData(presenter); } } void AudioGroupCollection::update(AudioGroupFilePresenter* presenter) { NSFileManager* fman = [NSFileManager defaultManager]; NSArray* contents = [fman contentsOfDirectoryAtURL:m_url includingPropertiesForKeys:@[NSURLIsDirectoryKey] options:NSDirectoryEnumerationSkipsSubdirectoryDescendants | NSDirectoryEnumerationSkipsHiddenFiles error:nil]; if (!contents) return; for (NSURL* path in contents) { NSNumber* isDir; [path getResourceValue:&isDir forKey:NSURLIsDirectoryKey error:nil]; if (isDir.boolValue) { auto search = m_groups.find(path.lastPathComponent.UTF8String); if (search == m_groups.end()) { std::string nameStr = path.lastPathComponent.UTF8String; search = m_groups.emplace(nameStr, std::make_unique(nameStr, [path URLByAppendingPathComponent:@"proj"], [path URLByAppendingPathComponent:@"pool"], [path URLByAppendingPathComponent:@"sdir"], [path URLByAppendingPathComponent:@"samp"], [path URLByAppendingPathComponent:@"meta"])).first; search->second->_attemptLoad(presenter); } } } } bool AudioGroupCollection::doSearch(std::string_view str) { bool ret = false; m_filterGroups.clear(); m_filterGroups.reserve(m_groups.size()); for (auto it = m_groups.begin() ; it != m_groups.end() ; ++it) // TODO: Heterogeneous lookup when C++20 available if (str.empty() || StrToLower(it->first).find(str.data()) != std::string::npos) { m_filterGroups.push_back(it); ret = true; } return ret; } bool AudioGroupCollection::doActiveFilter() { bool ret = false; m_filterGroups.clear(); m_filterGroups.reserve(m_groups.size()); for (auto it = m_groups.begin() ; it != m_groups.end() ; ++it) if (it->second->m_metaData->active) { m_filterGroups.push_back(it); ret = true; } return ret; } void AudioGroupCollection::addSFX(std::vector& vecOut) { for (auto it = m_groups.begin() ; it != m_groups.end() ; ++it) { if (!it->second->m_metaData->active) continue; const auto& sfxGroups = it->second->m_loadedGroup->getProj().sfxGroups(); std::map sortGroups; for (const auto& pair : sfxGroups) sortGroups[pair.first] = &pair.second; for (const auto& pair : sortGroups) { NSMutableAttributedString* name = [[NSMutableAttributedString alloc] initWithString:m_url.lastPathComponent attributes:@{NSForegroundColorAttributeName: [NSColor grayColor]}]; [name appendAttributedString:[[NSAttributedString alloc] initWithString:[NSString stringWithFormat:@" %s (%d)", it->first.c_str(), pair.first]]]; vecOut.push_back([[AudioGroupSFXToken alloc] initWithName:name loadId:0 sfx:nil]); std::map sortSfx; for (const auto& pair : pair.second->m_sfxEntries) sortSfx[pair.first] = pair.second; for (const auto& sfx : sortSfx) { name = [[NSMutableAttributedString alloc] initWithString:[NSString stringWithFormat:@"%d", sfx.first]]; vecOut.push_back([[AudioGroupSFXToken alloc] initWithName:name loadId:sfx.first sfx:sfx.second]); } } } } void AudioGroupCollection::addSamples(std::vector& vecOut) { for (auto it = m_groups.begin() ; it != m_groups.end() ; ++it) { if (!it->second->m_metaData->active) continue; const auto& samps = it->second->m_loadedGroup->getSdir().sampleEntries(); std::map*> sortSamps; for (const auto& pair : samps) sortSamps[pair.first] = &pair.second; NSMutableAttributedString* name = [[NSMutableAttributedString alloc] initWithString:m_url.lastPathComponent attributes:@{NSForegroundColorAttributeName: [NSColor grayColor]}]; [name appendAttributedString:[[NSAttributedString alloc] initWithString:[NSString stringWithFormat:@" %s", it->first.c_str()]]]; vecOut.push_back([[AudioGroupSampleToken alloc] initWithName:name samp:nil]); for (const auto& pair : sortSamps) { name = [[NSMutableAttributedString alloc] initWithString:[NSString stringWithFormat:@"%d", pair.first]]; vecOut.push_back([[AudioGroupSampleToken alloc] initWithName:name samp:pair.second]); } } } AudioGroupDataCollection::AudioGroupDataCollection(std::string_view name, NSURL* proj, NSURL* pool, NSURL* sdir, NSURL* samp, NSURL* meta) : m_name(name), m_proj(proj), m_pool(pool), m_sdir(sdir), m_samp(samp), m_meta(meta), m_token([[AudioGroupDataToken alloc] initWithDataCollection:this]) {} bool AudioGroupDataCollection::_attemptLoad(AudioGroupFilePresenter* presenter) { if (m_metaData && m_loadedData && m_loadedGroup) return true; if (!loadProj(presenter)) return false; if (!loadPool(presenter)) return false; if (!loadSdir(presenter)) return false; if (!loadSamp(presenter)) return false; if (!loadMeta(presenter)) return false; return _indexData(presenter); } bool AudioGroupDataCollection::_indexData(AudioGroupFilePresenter* presenter) { amuse::Engine& engine = [presenter->m_audioGroupClient getAmuseEngine]; switch (m_metaData->fmt) { case amuse::DataFormat::GCN: default: m_loadedData.emplace(m_projData.data(), m_projData.size(), m_poolData.data(), m_poolData.size(), m_sdirData.data(), m_sdirData.size(), m_sampData.data(), m_sampData.size(), amuse::GCNDataTag{}); break; case amuse::DataFormat::N64: m_loadedData.emplace(m_projData.data(), m_projData.size(), m_poolData.data(), m_poolData.size(), m_sdirData.data(), m_sdirData.size(), m_sampData.data(), m_sampData.size(), m_metaData->absOffs, amuse::N64DataTag{}); break; case amuse::DataFormat::PC: m_loadedData.emplace(m_projData.data(), m_projData.size(), m_poolData.data(), m_poolData.size(), m_sdirData.data(), m_sdirData.size(), m_sampData.data(), m_sampData.size(), m_metaData->absOffs, amuse::PCDataTag{}); break; } m_loadedGroup = engine.addAudioGroup(*m_loadedData); m_groupTokens.clear(); if (m_loadedGroup) { m_groupTokens.reserve(m_loadedGroup->getProj().songGroups().size() + m_loadedGroup->getProj().sfxGroups().size()); { const auto& songGroups = m_loadedGroup->getProj().songGroups(); std::map sortGroups; for (const auto& pair : songGroups) sortGroups[pair.first] = &pair.second; for (const auto& pair : sortGroups) { NSString* name = [NSString stringWithFormat:@"%d", pair.first]; m_groupTokens.push_back([[AudioGroupToken alloc] initWithName:name id:pair.first songGroup:pair.second]); } } { const auto& sfxGroups = m_loadedGroup->getProj().sfxGroups(); std::map sortGroups; for (const auto& pair : sfxGroups) sortGroups[pair.first] = &pair.second; for (const auto& pair : sortGroups) { NSString* name = [NSString stringWithFormat:@"%d", pair.first]; m_groupTokens.push_back([[AudioGroupToken alloc] initWithName:name id:pair.first sfxGroup:pair.second]); } } } return m_loadedData && m_loadedGroup; } void AudioGroupDataCollection::enable(AudioGroupFilePresenter* presenter) { m_metaData->active = true; NSFileCoordinator* coord = [[NSFileCoordinator alloc] initWithFilePresenter:presenter]; [coord coordinateWritingItemAtURL:m_meta options:0 error:nil byAccessor:^(NSURL* newUrl) { FILE* fp = fopen(newUrl.path.UTF8String, "wb"); if (fp) { fwrite(&*m_metaData, 1, sizeof(*m_metaData), fp); fclose(fp); } }]; } void AudioGroupDataCollection::disable(AudioGroupFilePresenter* presenter) { m_metaData->active = false; NSFileCoordinator* coord = [[NSFileCoordinator alloc] initWithFilePresenter:presenter]; [coord coordinateWritingItemAtURL:m_meta options:0 error:nil byAccessor:^(NSURL* newUrl) { FILE* fp = fopen(newUrl.path.UTF8String, "wb"); if (fp) { fwrite(&*m_metaData, 1, sizeof(*m_metaData), fp); fclose(fp); } }]; } void AudioGroupDataCollection::moveURL(NSURL* oldUrl, NSURL* newUrl) { if (m_proj) { if ([m_proj isEqual:oldUrl]) m_proj = newUrl; } if (m_pool) { if ([m_pool isEqual:oldUrl]) m_pool = newUrl; } if (m_sdir) { if ([m_sdir isEqual:oldUrl]) m_sdir = newUrl; } if (m_samp) { if ([m_samp isEqual:oldUrl]) m_samp = newUrl; } } bool AudioGroupDataCollection::loadProj(AudioGroupFilePresenter* presenter) { if (!m_proj) return false; NSFileCoordinator* coord = [[NSFileCoordinator alloc] initWithFilePresenter:presenter]; if (!coord) return false; NSError* err; __block std::vector& ret = m_projData; [coord coordinateReadingItemAtURL:m_proj options:NSFileCoordinatorReadingResolvesSymbolicLink error:&err byAccessor:^(NSURL* newUrl) { athena::io::FileReader r([[newUrl path] UTF8String], 1024 * 32, false); if (r.hasError()) return; size_t len = r.length(); ret.resize(len); r.readUBytesToBuf(ret.data(), len); }]; return ret.size(); } bool AudioGroupDataCollection::loadPool(AudioGroupFilePresenter* presenter) { if (!m_pool) return false; NSFileCoordinator* coord = [[NSFileCoordinator alloc] initWithFilePresenter:presenter]; if (!coord) return false; NSError* err; __block std::vector& ret = m_poolData; [coord coordinateReadingItemAtURL:m_pool options:NSFileCoordinatorReadingResolvesSymbolicLink error:&err byAccessor:^(NSURL* newUrl) { athena::io::FileReader r([[newUrl path] UTF8String], 1024 * 32, false); if (r.hasError()) return; size_t len = r.length(); ret.resize(len); r.readUBytesToBuf(ret.data(), len); }]; return ret.size(); } bool AudioGroupDataCollection::loadSdir(AudioGroupFilePresenter* presenter) { if (!m_sdir) return false; NSFileCoordinator* coord = [[NSFileCoordinator alloc] initWithFilePresenter:presenter]; if (!coord) return false; NSError* err; __block std::vector& ret = m_sdirData; [coord coordinateReadingItemAtURL:m_sdir options:NSFileCoordinatorReadingResolvesSymbolicLink error:&err byAccessor:^(NSURL* newUrl) { athena::io::FileReader r([[newUrl path] UTF8String], 1024 * 32, false); if (r.hasError()) return; size_t len = r.length(); ret.resize(len); r.readUBytesToBuf(ret.data(), len); }]; return ret.size(); } bool AudioGroupDataCollection::loadSamp(AudioGroupFilePresenter* presenter) { if (!m_samp) return false; NSFileCoordinator* coord = [[NSFileCoordinator alloc] initWithFilePresenter:presenter]; if (!coord) return false; NSError* err; __block std::vector& ret = m_sampData; [coord coordinateReadingItemAtURL:m_samp options:NSFileCoordinatorReadingResolvesSymbolicLink error:&err byAccessor:^(NSURL* newUrl) { athena::io::FileReader r([[newUrl path] UTF8String], 1024 * 32, false); if (r.hasError()) return; size_t len = r.length(); ret.resize(len); r.readUBytesToBuf(ret.data(), len); }]; return ret.size(); } bool AudioGroupDataCollection::loadMeta(AudioGroupFilePresenter* presenter) { if (!m_meta) return false; NSFileCoordinator* coord = [[NSFileCoordinator alloc] initWithFilePresenter:presenter]; if (!coord) return false; NSError* err; __block std::optional& ret = m_metaData; [coord coordinateReadingItemAtURL:m_meta options:NSFileCoordinatorReadingResolvesSymbolicLink error:&err byAccessor:^(NSURL* newUrl) { athena::io::FileReader r([[newUrl path] UTF8String], 1024 * 32, false); if (r.hasError()) return; ret.emplace(r); }]; return ret.operator bool(); } - (void)presentedSubitemDidChangeAtURL:(NSURL *)url { size_t relComps = url.pathComponents.count - m_groupURL.pathComponents.count; if (relComps <= 1) [self update]; } - (void)presentedSubitemAtURL:(NSURL*)oldUrl didMoveToURL:(NSURL*)newUrl { for (auto& pair : m_audioGroupCollections) { for (auto& pair2 : pair.second->m_groups) { pair2.second->moveURL(oldUrl, newUrl); } } } - (NSInteger)outlineView:(NSOutlineView*)outlineView numberOfChildrenOfItem:(nullable id)item { m_lastOutlineView = outlineView; if (!item) return m_filterAudioGroupCollections.size(); AudioGroupCollection& collection = *((AudioGroupCollectionToken*)item)->m_collection; return collection.m_filterGroups.size(); } - (id)outlineView:(NSOutlineView*)outlineView child:(NSInteger)index ofItem:(nullable id)item { if (!item) { if (index >= m_filterAudioGroupCollections.size()) return nil; return m_filterAudioGroupCollections[index]->second->m_token; } AudioGroupCollection& collection = *((AudioGroupCollectionToken*)item)->m_collection; if (index >= collection.m_filterGroups.size()) return nil; return collection.m_filterGroups[index]->second->m_token; } - (BOOL)outlineView:(NSOutlineView*)outlineView isItemExpandable:(id)item { if ([item isKindOfClass:[AudioGroupCollectionToken class]]) return YES; return NO; } - (id)outlineView:(NSOutlineView *)outlineView objectValueForTableColumn:(NSTableColumn *)tableColumn byItem:(id)item { if ([item isKindOfClass:[AudioGroupCollectionToken class]]) { AudioGroupCollection& collection = *((AudioGroupCollectionToken*)item)->m_collection; if ([tableColumn.identifier isEqualToString:@"CollectionColumn"]) { size_t totalOn = 0; for (auto& pair : collection.m_groups) if (pair.second->m_metaData->active) ++totalOn; if (totalOn == 0) return [NSNumber numberWithInt:NSOffState]; else if (totalOn == collection.m_groups.size()) return [NSNumber numberWithInt:NSOnState]; else return [NSNumber numberWithInt:NSMixedState]; } else if ([tableColumn.identifier isEqualToString:@"DetailsColumn"]) return [NSString stringWithFormat:@"%zu Group File%s", collection.m_groups.size(), collection.m_groups.size() > 1 ? "s" : ""]; } else if ([item isKindOfClass:[AudioGroupDataToken class]]) { AudioGroupDataCollection& data = *((AudioGroupDataToken*)item)->m_collection; if ([tableColumn.identifier isEqualToString:@"CollectionColumn"]) return [NSNumber numberWithInt:data.m_metaData->active ? NSOnState : NSOffState]; else if ([tableColumn.identifier isEqualToString:@"DetailsColumn"]) { if (!data.m_loadedGroup) return @""; if (data.m_loadedGroup->getProj().songGroups().size() && data.m_loadedGroup->getProj().sfxGroups().size()) return [NSString stringWithFormat:@"%zu Song Group%s, %zu SFX Group%s", data.m_loadedGroup->getProj().songGroups().size(), data.m_loadedGroup->getProj().songGroups().size() > 1 ? "s" : "", data.m_loadedGroup->getProj().sfxGroups().size(), data.m_loadedGroup->getProj().sfxGroups().size() > 1 ? "s" : ""]; else if (data.m_loadedGroup->getProj().songGroups().size()) return [NSString stringWithFormat:@"%zu Song Group%s", data.m_loadedGroup->getProj().songGroups().size(), data.m_loadedGroup->getProj().songGroups().size() > 1 ? "s" : ""]; else if (data.m_loadedGroup->getProj().sfxGroups().size()) return [NSString stringWithFormat:@"%zu SFX Group%s", data.m_loadedGroup->getProj().sfxGroups().size(), data.m_loadedGroup->getProj().sfxGroups().size() > 1 ? "s" : ""]; else return @""; } } return nil; } - (void)outlineView:(NSOutlineView *)outlineView setObjectValue:(nullable id)object forTableColumn:(nullable NSTableColumn *)tableColumn byItem:(nullable id)item { bool dirty = false; if ([item isKindOfClass:[AudioGroupCollectionToken class]]) { AudioGroupCollection& collection = *((AudioGroupCollectionToken*)item)->m_collection; if ([tableColumn.identifier isEqualToString:@"CollectionColumn"]) { NSInteger active = [object integerValue]; if (active) for (auto& pair : collection.m_groups) pair.second->enable(self); else for (auto& pair : collection.m_groups) pair.second->disable(self); dirty = true; } } else if ([item isKindOfClass:[AudioGroupDataToken class]]) { AudioGroupDataCollection& data = *((AudioGroupDataToken*)item)->m_collection; if ([tableColumn.identifier isEqualToString:@"CollectionColumn"]) { NSInteger active = [object integerValue]; if (active) data.enable(self); else data.disable(self); dirty = true; } } if (dirty) [self resetIterators]; } - (void)outlineView:(NSOutlineView *)outlineView willDisplayCell:(nonnull id)cell forTableColumn:(nullable NSTableColumn *)tableColumn item:(nonnull id)item { if ([item isKindOfClass:[AudioGroupCollectionToken class]]) { AudioGroupCollection& collection = *((AudioGroupCollectionToken*)item)->m_collection; if ([tableColumn.identifier isEqualToString:@"CollectionColumn"]) ((NSButtonCell*)cell).title = collection.m_url.lastPathComponent; } else if ([item isKindOfClass:[AudioGroupDataToken class]]) { AudioGroupDataCollection& data = *((AudioGroupDataToken*)item)->m_collection; if ([tableColumn.identifier isEqualToString:@"CollectionColumn"]) ((NSButtonCell*)cell).title = @(data.m_name.c_str()); } } - (void)outlineViewSelectionDidChange:(NSNotification *)notification { DataOutlineView* ov = notification.object; id item = [ov itemAtRow:ov.selectedRow]; [(AppDelegate*)NSApp.delegate outlineView:ov selectionChanged:item]; } - (NSDragOperation)outlineView:(NSOutlineView *)outlineView validateDrop:(id)info proposedItem:(id)item proposedChildIndex:(NSInteger)index { [outlineView setDropItem:nil dropChildIndex:NSOutlineViewDropOnItemIndex]; NSPasteboard* pboard = [info draggingPasteboard]; if ([[pboard types] containsObject:NSURLPboardType]) return NSDragOperationCopy; return NSDragOperationNone; } - (BOOL)outlineView:(NSOutlineView *)outlineView acceptDrop:(id)info item:(id)item childIndex:(NSInteger)index { NSPasteboard* pboard = [info draggingPasteboard]; if ([[pboard types] containsObject:NSURLPboardType]) { NSURL* url = [NSURL URLFromPasteboard:pboard]; [(AppDelegate*)NSApp.delegate importURL:url]; return YES; } return NO; } - (BOOL)addCollectionName:(std::string&&)name items:(std::vector>&&)collection { NSFileCoordinator* coord = [[NSFileCoordinator alloc] initWithFilePresenter:self]; if (!coord) return false; NSURL* dir = [m_groupURL URLByAppendingPathComponent:@(name.c_str())]; __block AudioGroupCollection& insert = *m_audioGroupCollections.emplace(name, std::make_unique(dir)).first->second; insert.addCollection(self, std::move(collection)); [coord coordinateWritingItemAtURL:m_groupURL options:0 error:nil byAccessor:^(NSURL* newUrl) { for (std::pair>& pair : insert.m_groups) { NSURL* collectionUrl = [insert.m_url URLByAppendingPathComponent:@(pair.first.c_str())]; [[NSFileManager defaultManager] createDirectoryAtURL:collectionUrl withIntermediateDirectories:YES attributes:nil error:nil]; FILE* fp = fopen(pair.second->m_proj.path.UTF8String, "wb"); if (fp) { fwrite(pair.second->m_projData.data(), 1, pair.second->m_projData.size(), fp); fclose(fp); } fp = fopen(pair.second->m_pool.path.UTF8String, "wb"); if (fp) { fwrite(pair.second->m_poolData.data(), 1, pair.second->m_poolData.size(), fp); fclose(fp); } fp = fopen(pair.second->m_sdir.path.UTF8String, "wb"); if (fp) { fwrite(pair.second->m_sdirData.data(), 1, pair.second->m_sdirData.size(), fp); fclose(fp); } fp = fopen(pair.second->m_samp.path.UTF8String, "wb"); if (fp) { fwrite(pair.second->m_sampData.data(), 1, pair.second->m_sampData.size(), fp); fclose(fp); } fp = fopen(pair.second->m_meta.path.UTF8String, "wb"); if (fp) { fwrite(&*pair.second->m_metaData, 1, sizeof(*pair.second->m_metaData), fp); fclose(fp); } } }]; [self resetIterators]; return true; } - (void)update { NSFileCoordinator* coord = [[NSFileCoordinator alloc] initWithFilePresenter:self]; if (!coord) return; NSError* coordErr; __block NSError* managerErr; __block std::map>& theMap = m_audioGroupCollections; __block AudioGroupFilePresenter* presenter = self; [coord coordinateReadingItemAtURL:m_groupURL options:NSFileCoordinatorReadingResolvesSymbolicLink error:&coordErr byAccessor:^(NSURL* newUrl) { NSFileManager* fman = [NSFileManager defaultManager]; NSArray* contents = [fman contentsOfDirectoryAtURL:newUrl includingPropertiesForKeys:@[NSURLIsDirectoryKey] options:NSDirectoryEnumerationSkipsSubdirectoryDescendants | NSDirectoryEnumerationSkipsHiddenFiles error:&managerErr]; if (!contents) return; for (NSURL* path in contents) { NSNumber* isDir; [path getResourceValue:&isDir forKey:NSURLIsDirectoryKey error:nil]; if (isDir.boolValue) { auto search = theMap.find(path.lastPathComponent.UTF8String); if (search == theMap.end()) { search = theMap.emplace(path.lastPathComponent.UTF8String, std::make_unique(path)).first; search->second->update(presenter); } } } }]; [self resetIterators]; } - (void)resetIterators { if ([(NSObject*)m_audioGroupClient isKindOfClass:[AppDelegate class]]) { std::string search; if (m_searchStr) search = m_searchStr.UTF8String; m_sfxTableData.clear(); m_sampleTableData.clear(); m_filterAudioGroupCollections.clear(); m_filterAudioGroupCollections.reserve(m_audioGroupCollections.size()); for (auto it = m_audioGroupCollections.begin() ; it != m_audioGroupCollections.end() ; ++it) { it->second->addSFX(m_sfxTableData); it->second->addSamples(m_sampleTableData); if (it->second->doSearch(search) || !m_searchStr || StrToLower(it->first).find(search) != std::string::npos) m_filterAudioGroupCollections.push_back(it); } [m_lastOutlineView reloadItem:nil reloadChildren:YES]; [(AppDelegate*)m_audioGroupClient reloadTables]; } else { m_sfxTableData.clear(); m_sampleTableData.clear(); m_filterAudioGroupCollections.clear(); m_filterAudioGroupCollections.reserve(m_audioGroupCollections.size()); for (auto it = m_audioGroupCollections.begin() ; it != m_audioGroupCollections.end() ; ++it) { it->second->addSFX(m_sfxTableData); it->second->addSamples(m_sampleTableData); if (it->second->doActiveFilter()) m_filterAudioGroupCollections.push_back(it); } [((AmuseAudioUnit*)m_audioGroupClient)->m_viewController->m_groupBrowser loadColumnZero]; } } - (void)setSearchFilter:(NSString*)str { m_searchStr = [str lowercaseString]; [self resetIterators]; } - (void)removeSelectedItem { id item = [m_lastOutlineView itemAtRow:m_lastOutlineView.selectedRow]; if ([item isKindOfClass:[AudioGroupCollectionToken class]]) { AudioGroupCollection& collection = *((AudioGroupCollectionToken*)item)->m_collection; NSURL* collectionURL = collection.m_url; NSString* lastComp = collectionURL.lastPathComponent; m_audioGroupCollections.erase(lastComp.UTF8String); [self resetIterators]; [[NSFileManager defaultManager] removeItemAtURL:collectionURL error:nil]; if (m_audioGroupCollections.empty()) [(AppDelegate*)NSApp.delegate outlineView:(DataOutlineView*)m_lastOutlineView selectionChanged:nil]; } } - (id)initWithAudioGroupClient:(id)client { m_audioGroupClient = client; m_groupURL = [[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:@"group.com.axiodl.Amuse.AudioGroups"]; if (!m_groupURL) return nil; [NSFileCoordinator addFilePresenter:self]; [self update]; return self; } - (void)dealloc { [NSFileCoordinator removeFilePresenter:self]; } @end