amuse/AudioUnit/AudioGroupFilePresenter.mm

867 lines
32 KiB
Plaintext

#include "AudioGroupFilePresenter.hpp"
#include <athena/FileReader.hpp>
#include <amuse/AudioGroupProject.hpp>
#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<amuse::AudioGroupSampleDirectory::Entry,
amuse::AudioGroupSampleDirectory::ADPCMParms>*)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<std::pair<std::string, amuse::IntrusiveAudioGroupData>>&& collection)
{
for (std::pair<std::string, amuse::IntrusiveAudioGroupData>& 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<AudioGroupDataCollection>(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<NSURL*>* 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<AudioGroupDataCollection>(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<AudioGroupSFXToken*>& 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<int, const amuse::SFXGroupIndex*> 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<int, const amuse::SFXGroupIndex::SFXEntry*> 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<AudioGroupSampleToken*>& 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<int, const std::pair<amuse::AudioGroupSampleDirectory::Entry, amuse::AudioGroupSampleDirectory::ADPCMParms>*> 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<int, const amuse::SongGroupIndex*> 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<int, const amuse::SFXGroupIndex*> 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<uint8_t>& 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<uint8_t>& 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<uint8_t>& 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<uint8_t>& 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<MetaData>& 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<NSDraggingInfo>)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<NSDraggingInfo>)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<std::pair<std::string, amuse::IntrusiveAudioGroupData>>&&)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<AudioGroupCollection>(dir)).first->second;
insert.addCollection(self, std::move(collection));
[coord coordinateWritingItemAtURL:m_groupURL options:0 error:nil
byAccessor:^(NSURL* newUrl)
{
for (std::pair<const std::string, std::unique_ptr<AudioGroupDataCollection>>& 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<std::string, std::unique_ptr<AudioGroupCollection>>& theMap = m_audioGroupCollections;
__block AudioGroupFilePresenter* presenter = self;
[coord coordinateReadingItemAtURL:m_groupURL options:NSFileCoordinatorReadingResolvesSymbolicLink error:&coordErr
byAccessor:^(NSURL* newUrl)
{
NSFileManager* fman = [NSFileManager defaultManager];
NSArray<NSURL*>* 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<AudioGroupCollection>(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<AudioGroupClient>)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