Move bundle loading to own file

This commit is contained in:
Allan Odgaard
2013-03-17 16:02:30 +01:00
parent 0f9b15b916
commit f888376e05
3 changed files with 432 additions and 417 deletions

View File

@@ -1,11 +1,9 @@
#include "index.h"
#include "locations.h"
#include "fsevents/fs_controller.h"
#include "query.h" // set_index
#include "query.h" // required by item_t::environment
#include "locations.h" // required by item_t::save
#include <plist/delta.h>
#include <text/case.h>
#include <io/path.h>
#include <text/format.h>
#include <regexp/glob.h>
#include <regexp/format_string.h>
namespace bundles
@@ -35,13 +33,9 @@ namespace bundles
std::string const kFieldSettingName = "settings"; // dictionary
std::string const kFieldRequiredItems = "require";
std::string const kFieldDeletedItems = "deleted";
std::string const kFieldChangedItems = "changed";
std::string const kFieldMainMenu = "mainMenu";
std::string const kFieldAny = NULL_STR;
std::string const kSeparatorString = "------------------------------------";
oak::uuid_t const kSeparatorUUID = "AB21B655-D3BE-4EAD-82C9-E8CFF02B2913";
// ==========
@@ -388,412 +382,4 @@ namespace bundles
return res;
}
// ===================
// = Index Functions =
// ===================
static fs::node_t resolve (std::string& cwd, fs::node_t node, std::map<std::string, fs::node_t> const& heads)
{
while(node.type() == fs::node_t::kNodeTypeLink)
{
std::map<std::string, fs::node_t>::const_iterator it = heads.find(cwd);
if(it == heads.end())
break;
node = it->second;
cwd = node.real_path(path::parent(cwd));
}
return node;
}
static std::vector<oak::uuid_t> to_menu (plist::array_t const& uuids)
{
std::vector<oak::uuid_t> res;
iterate(uuid, uuids)
{
if(std::string const* str = boost::get<std::string>(&*uuid))
res.push_back(*str == kSeparatorString ? kSeparatorUUID : oak::uuid_t(*str));
}
return res;
}
namespace
{
struct delta_item_t
{
delta_item_t (bundles::item_ptr item, plist::dictionary_t const& plist) : item(item), plist(plist) { }
bundles::item_ptr item;
plist::dictionary_t plist;
};
struct property_cache_t
{
property_cache_t (std::string const& cacheFile) : _cache_file(cacheFile), _dirty(false)
{
_cache = plist::load(_cache_file);
}
~property_cache_t ()
{
std::vector<std::string> knownPaths, deadPaths;
std::transform(_cache.begin(), _cache.end(), back_inserter(knownPaths), [](std::pair<std::string, plist::any_t> const& p){ return p.first; });
std::set_difference(knownPaths.begin(), knownPaths.end(), _accessed_paths.begin(), _accessed_paths.end(), back_inserter(deadPaths));
if(!deadPaths.empty())
{
iterate(path, deadPaths)
_cache.erase(*path);
_dirty = true;
}
if(_dirty)
plist::save(_cache_file, _cache);
}
plist::dictionary_t const& get (std::string const& path, time_t modified)
{
_accessed_paths.insert(path);
plist::dictionary_t::const_iterator cachedNode = _cache.find(path);
if(cachedNode != _cache.end() && boost::get<plist::dictionary_t>(&cachedNode->second))
{
plist::dictionary_t const& node = boost::get<plist::dictionary_t>(cachedNode->second);
oak::date_t cacheDate;
if(plist::get_key_path(node, "modified", cacheDate) && cacheDate == oak::date_t(modified))
{
plist::dictionary_t::const_iterator res = node.find("values");
if(res != node.end() && boost::get<plist::dictionary_t>(&res->second))
return boost::get<plist::dictionary_t>(res->second);
fprintf(stderr, "error looking for %s in cache\n", path.c_str());
}
}
plist::dictionary_t cacheEntry;
cacheEntry["modified"] = oak::date_t(modified);
cacheEntry["values"] = prune_dictionary(plist::load(path));
_cache[path] = cacheEntry;
_dirty = true;
return get(path, modified);
}
private:
static plist::dictionary_t prune_dictionary (plist::dictionary_t const& plist)
{
static std::set<std::string> const DesiredKeys = { kFieldName, kFieldKeyEquivalent, kFieldTabTrigger, kFieldScopeSelector, kFieldSemanticClass, kFieldContentMatch, kFieldGrammarFirstLineMatch, kFieldGrammarScope, kFieldGrammarInjectionSelector, kFieldDropExtension, kFieldGrammarExtension, kFieldSettingName, kFieldHideFromUser, kFieldIsDeleted, kFieldIsDisabled, kFieldRequiredItems, kFieldUUID, kFieldMainMenu, kFieldIsDelta, kFieldDeletedItems, kFieldChangedItems };
plist::dictionary_t res;
citerate(pair, plist)
{
if(DesiredKeys.find(pair->first) == DesiredKeys.end() && pair->first.find(kFieldSettingName) != 0)
continue;
if(pair->first == kFieldSettingName)
{
if(plist::dictionary_t const* dictionary = boost::get<plist::dictionary_t>(&pair->second))
{
plist::dictionary_t prunedDict;
iterate(settingsPair, *dictionary)
prunedDict.insert(std::make_pair(settingsPair->first, true));
res.insert(std::make_pair(pair->first, prunedDict));
}
}
else if(pair->first == kFieldChangedItems)
{
if(plist::dictionary_t const* dictionary = boost::get<plist::dictionary_t>(&pair->second))
res.insert(std::make_pair(pair->first, prune_dictionary(*dictionary)));
}
else
{
res.insert(*pair);
}
}
return res;
}
std::string _cache_file;
plist::dictionary_t _cache;
std::set<std::string> _accessed_paths;
bool _dirty;
};
}
void item_t::traverse (std::map<std::string, fs::node_t> const& heads, std::string const& cacheFile)
{
property_cache_t plistCache(cacheFile);
std::vector<item_ptr> items(1, item_t::menu_item_separator());
std::map< oak::uuid_t, std::vector<oak::uuid_t> > menus;
std::map<oak::uuid_t, delta_item_t> deltaItems;
std::set< std::pair<oak::uuid_t, oak::uuid_t> > hiddenItems, deletedItems;
std::set<oak::uuid_t> loadedItems;
bool local = true;
iterate(path, locations())
{
std::string cwd = path::join(*path, "Bundles");
std::map<std::string, fs::node_t>::const_iterator node = heads.find(cwd);
if(node == heads.end() || node->second.type() == fs::node_t::kNodeTypeMissing)
{
local = false;
continue;
}
fs::node_t bundlesNode = resolve(cwd, node->second, heads);
if(bundlesNode.type() != fs::node_t::kNodeTypeDirectory || !bundlesNode.entries())
{
fprintf(stderr, "*** no bundles for path %s\n", bundlesNode.real_path(cwd).c_str());
continue;
}
citerate(fsBundle, *bundlesNode.entries())
{
if(text::lowercase(path::extension(fsBundle->name())) != ".tmbundle")
{
fprintf(stderr, "not a bundle: %s\n", fsBundle->name().c_str());
continue;
}
std::string bundlePath = fsBundle->real_path(cwd);
fs::node_t bundleNode = resolve(bundlePath, *fsBundle, heads);
if(bundleNode.type() != fs::node_t::kNodeTypeDirectory)
{
fprintf(stderr, "not a directory: %s (%s)\n", bundleNode.name().c_str(), bundleNode.path(cwd).c_str());
continue;
}
item_ptr bundle;
citerate(infoPlist, *bundleNode.entries())
{
if(infoPlist->name() != "info.plist")
continue;
std::string infoPlistPath = infoPlist->real_path(bundlePath);
fs::node_t infoPlistNode = resolve(infoPlistPath, *infoPlist, heads);
if(infoPlistNode.type() != fs::node_t::kNodeTypeFile)
break;
oak::uuid_t bundleUUID;
plist::dictionary_t plist = plistCache.get(infoPlistPath, infoPlistNode.modified());
if(!plist::get_key_path(plist, kFieldUUID, bundleUUID))
break;
bundle.reset(new item_t(bundleUUID, item_ptr(), kItemTypeBundle, local));
bundle->add_path(infoPlistPath);
bool isDelta = false;
if(plist::get_key_path(plist, kFieldIsDelta, isDelta) && isDelta)
{
deltaItems.insert(std::make_pair(bundleUUID, delta_item_t(bundle, plist)));
break;
}
std::map<oak::uuid_t, delta_item_t>::iterator deltaItem = deltaItems.find(bundleUUID);
if(deltaItem != deltaItems.end())
{
bundle = deltaItem->second.item;
bundle->add_path(infoPlistPath);
std::vector<plist::dictionary_t> plists;
plists.push_back(deltaItem->second.plist);
plists.push_back(plist);
plist = plist::merge_delta(plists);
deltaItems.erase(deltaItem);
}
else if(loadedItems.find(bundleUUID) != loadedItems.end())
{
// fprintf(stderr, "skip %s (already loaded)\n", infoPlistPath.c_str());
break;
}
bundle->initialize(plist);
items.push_back(bundle);
plist::array_t uuids;
plist::get_key_path(plist, kFieldDeletedItems, uuids);
iterate(uuid, uuids)
{
if(std::string const* str = boost::get<std::string>(&*uuid))
deletedItems.insert(std::make_pair(*str, bundleUUID));
}
// ====================
// = Load Bundle Menu =
// ====================
plist::array_t mainMenu;
plist::get_key_path(plist, "mainMenu.items", mainMenu);
menus.insert(std::make_pair(bundleUUID, to_menu(mainMenu)));
plist::dictionary_t subMenus;
plist::get_key_path(plist, "mainMenu.submenus", subMenus);
iterate(submenuIter, subMenus)
{
std::string name;
plist::array_t uuids;
if(plist::get_key_path(submenuIter->second, kFieldName, name) && plist::get_key_path(submenuIter->second, "items", uuids))
{
item_ptr item(new item_t(submenuIter->first, bundle, kItemTypeMenu));
item->_uuid = submenuIter->first;
item->_fields.insert(std::make_pair(kFieldName, name));
items.push_back(item);
menus.insert(std::make_pair(submenuIter->first, to_menu(uuids)));
}
}
plist::get_key_path(plist, "mainMenu.excludedItems", uuids);
iterate(uuid, uuids)
{
if(std::string const* str = boost::get<std::string>(&*uuid))
hiddenItems.insert(std::make_pair(*str, bundleUUID));
}
break;
}
if(!bundle)
{
fprintf(stderr, "no bundle: %s\n", bundlePath.c_str());
continue;
}
citerate(dir, *bundleNode.entries())
{
static struct { std::string name; std::string extension; kind_t kind; } const dirs[] =
{
{ "Commands", ".tmCommand", kItemTypeCommand },
{ "DragCommands", ".tmDragCommand", kItemTypeDragCommand },
{ "Macros", ".tmMacro", kItemTypeMacro },
{ "Preferences", ".tmPreferences", kItemTypeSettings },
{ "Snippets", ".tmSnippet", kItemTypeSnippet },
{ "Syntaxes", ".tmLanguage", kItemTypeGrammar },
{ "Proxies", ".tmProxy", kItemTypeProxy },
{ "Themes", ".tmTheme", kItemTypeTheme },
};
for(size_t i = 0; i < sizeofA(dirs); ++i)
{
if(dir->name() != dirs[i].name)
continue;
std::string dirPath = dir->real_path(bundlePath);
fs::node_t dirNode = resolve(dirPath, *dir, heads);
if(dirNode.type() != fs::node_t::kNodeTypeDirectory)
break;
std::string const extensions[] = { dirs[i].extension, ".tmDelta", ".plist" };
citerate(fsItem, *dirNode.entries())
{
if(!oak::contains(std::begin(extensions), std::end(extensions), path::extension(fsItem->name())))
{
fprintf(stderr, "wrong item type: %s\n", fsItem->name().c_str());
continue;
}
std::string itemPath = fsItem->real_path(dirPath);
fs::node_t itemNode = resolve(itemPath, *fsItem, heads);
if(itemNode.type() != fs::node_t::kNodeTypeFile)
continue;
plist::dictionary_t plist = plistCache.get(itemPath, itemNode.modified());
oak::uuid_t uuid;
if(!plist::get_key_path(plist, kFieldUUID, uuid))
continue;
if(loadedItems.find(uuid) != loadedItems.end())
{
// fprintf(stderr, "skip %s (already loaded)\n", itemPath.c_str());
continue;
}
item_ptr item(new item_t(uuid, bundle, dirs[i].kind, local));
item->add_path(itemPath);
bool isDelta = false;
if(plist::get_key_path(plist, kFieldIsDelta, isDelta) && isDelta)
{
deltaItems.insert(std::make_pair(uuid, delta_item_t(item, plist)));
continue;
}
std::map<oak::uuid_t, delta_item_t>::iterator deltaItem = deltaItems.find(uuid);
if(deltaItem != deltaItems.end())
{
item = deltaItem->second.item;
item->add_path(itemPath);
std::vector<plist::dictionary_t> plists;
plists.push_back(deltaItem->second.plist);
plists.push_back(plist);
plist = plist::merge_delta(plists);
deltaItems.erase(deltaItem);
}
else if(loadedItems.find(bundle->uuid()) != loadedItems.end())
{
// fprintf(stderr, "skip %s (bundle already loaded from more local path)\n", itemPath.c_str());
continue;
}
item->initialize(plist);
items.push_back(item);
loadedItems.insert(uuid);
}
break;
}
}
if(deltaItems.find(bundle->uuid()) == deltaItems.end())
loadedItems.insert(bundle->uuid());
}
local = false;
}
for(ssize_t i = items.size(); i-- > 0; )
{
item_ptr item = items[i];
if(deltaItems.find(item->bundle_uuid()) != deltaItems.end())
{
fprintf(stderr, "Warning: Bundle item %s at path %s has no (non-delta) parent\n", item->name().c_str(), text::join(item->paths(), ", ").c_str());
items.erase(items.begin() + i);
}
else
{
item->_hidden_from_user = hiddenItems.find(std::make_pair(item->uuid(), item->bundle_uuid())) != hiddenItems.end() ?: item->_hidden_from_user;
item->_deleted = deletedItems.find(std::make_pair(item->uuid(), item->bundle_uuid())) != deletedItems.end() ?: item->_deleted;
}
}
set_index(items, menus);
}
void build_index (std::string const& cacheDir)
{
static path::glob_t const dirGlob = "*.tm[Bb]undle{,/{Commands,DragCommands,Macros,Preferences,Proxies,Snippets,Syntaxes,Themes}}";
static path::glob_t const fileGlob = "*.tm[Bb]undle/{info.plist,Commands/*.{plist,tmCommand,tmDelta},DragCommands/*.{plist,tmDragCommand,tmDelta},Macros/*.{plist,tmMacro,tmDelta},Preferences/*.{plist,tmPreferences,tmDelta},Proxies/*.{tmProxy,tmDelta},Snippets/*.{plist,tmSnippet,tmDelta},Syntaxes/*.{plist,tmLanguage,tmDelta},Themes/*.{plist,tmTheme,tmDelta}}";
struct callback_t : fs::callback_t
{
callback_t (std::string const& cacheFile) : _cache_file(cacheFile) { }
void did_change (std::map<std::string, fs::node_t> const& heads, std::map< std::string, std::vector<std::string> > const& changes)
{
item_t::traverse(heads, _cache_file);
}
private:
std::string _cache_file;
};
std::set<std::string> rootPaths;
iterate(path, bundles::locations())
rootPaths.insert(path::join(*path, "Bundles"));
std::string const fsTreeCacheFile = path::join(cacheDir == NULL_STR ? "/tmp" : cacheDir, "FSNodes.plist");
std::string const plistCacheFile = path::join(cacheDir == NULL_STR ? "/tmp" : cacheDir, "PropertyValues.plist");
static fs::watch_info_ptr info = fs::watch_paths(rootPaths, new callback_t(plistCacheFile), dirGlob, fileGlob, fsTreeCacheFile);
}
} /* bundles */

View File

@@ -33,6 +33,11 @@ namespace bundles
PUBLIC extern std::string const kFieldAny;
PUBLIC extern std::string const kFieldIsDelta;
PUBLIC extern std::string const kFieldIsDeleted;
PUBLIC extern std::string const kFieldRequiredItems;
PUBLIC extern oak::uuid_t const kSeparatorUUID;
struct item_t;
typedef std::shared_ptr<item_t> item_ptr;

View File

@@ -0,0 +1,424 @@
#include "index.h"
#include "locations.h"
#include "query.h" // set_index
#include "fsevents/fs_controller.h"
#include <plist/delta.h>
#include <regexp/glob.h>
#include <text/case.h>
namespace bundles
{
static std::string const kFieldChangedItems = "changed";
static std::string const kFieldDeletedItems = "deleted";
static std::string const kFieldMainMenu = "mainMenu";
static std::string const kSeparatorString = "------------------------------------";
// ===================
// = Index Functions =
// ===================
static fs::node_t resolve (std::string& cwd, fs::node_t node, std::map<std::string, fs::node_t> const& heads)
{
while(node.type() == fs::node_t::kNodeTypeLink)
{
std::map<std::string, fs::node_t>::const_iterator it = heads.find(cwd);
if(it == heads.end())
break;
node = it->second;
cwd = node.real_path(path::parent(cwd));
}
return node;
}
static std::vector<oak::uuid_t> to_menu (plist::array_t const& uuids)
{
std::vector<oak::uuid_t> res;
iterate(uuid, uuids)
{
if(std::string const* str = boost::get<std::string>(&*uuid))
res.push_back(*str == kSeparatorString ? kSeparatorUUID : oak::uuid_t(*str));
}
return res;
}
namespace
{
struct delta_item_t
{
delta_item_t (bundles::item_ptr item, plist::dictionary_t const& plist) : item(item), plist(plist) { }
bundles::item_ptr item;
plist::dictionary_t plist;
};
struct property_cache_t
{
property_cache_t (std::string const& cacheFile) : _cache_file(cacheFile), _dirty(false)
{
_cache = plist::load(_cache_file);
}
~property_cache_t ()
{
std::vector<std::string> knownPaths, deadPaths;
std::transform(_cache.begin(), _cache.end(), back_inserter(knownPaths), [](std::pair<std::string, plist::any_t> const& p){ return p.first; });
std::set_difference(knownPaths.begin(), knownPaths.end(), _accessed_paths.begin(), _accessed_paths.end(), back_inserter(deadPaths));
if(!deadPaths.empty())
{
iterate(path, deadPaths)
_cache.erase(*path);
_dirty = true;
}
if(_dirty)
plist::save(_cache_file, _cache);
}
plist::dictionary_t const& get (std::string const& path, time_t modified)
{
_accessed_paths.insert(path);
plist::dictionary_t::const_iterator cachedNode = _cache.find(path);
if(cachedNode != _cache.end() && boost::get<plist::dictionary_t>(&cachedNode->second))
{
plist::dictionary_t const& node = boost::get<plist::dictionary_t>(cachedNode->second);
oak::date_t cacheDate;
if(plist::get_key_path(node, "modified", cacheDate) && cacheDate == oak::date_t(modified))
{
plist::dictionary_t::const_iterator res = node.find("values");
if(res != node.end() && boost::get<plist::dictionary_t>(&res->second))
return boost::get<plist::dictionary_t>(res->second);
fprintf(stderr, "error looking for %s in cache\n", path.c_str());
}
}
plist::dictionary_t cacheEntry;
cacheEntry["modified"] = oak::date_t(modified);
cacheEntry["values"] = prune_dictionary(plist::load(path));
_cache[path] = cacheEntry;
_dirty = true;
return get(path, modified);
}
private:
static plist::dictionary_t prune_dictionary (plist::dictionary_t const& plist)
{
static std::set<std::string> const DesiredKeys = { kFieldName, kFieldKeyEquivalent, kFieldTabTrigger, kFieldScopeSelector, kFieldSemanticClass, kFieldContentMatch, kFieldGrammarFirstLineMatch, kFieldGrammarScope, kFieldGrammarInjectionSelector, kFieldDropExtension, kFieldGrammarExtension, kFieldSettingName, kFieldHideFromUser, kFieldIsDeleted, kFieldIsDisabled, kFieldRequiredItems, kFieldUUID, kFieldMainMenu, kFieldIsDelta, kFieldDeletedItems, kFieldChangedItems };
plist::dictionary_t res;
citerate(pair, plist)
{
if(DesiredKeys.find(pair->first) == DesiredKeys.end() && pair->first.find(kFieldSettingName) != 0)
continue;
if(pair->first == kFieldSettingName)
{
if(plist::dictionary_t const* dictionary = boost::get<plist::dictionary_t>(&pair->second))
{
plist::dictionary_t prunedDict;
iterate(settingsPair, *dictionary)
prunedDict.insert(std::make_pair(settingsPair->first, true));
res.insert(std::make_pair(pair->first, prunedDict));
}
}
else if(pair->first == kFieldChangedItems)
{
if(plist::dictionary_t const* dictionary = boost::get<plist::dictionary_t>(&pair->second))
res.insert(std::make_pair(pair->first, prune_dictionary(*dictionary)));
}
else
{
res.insert(*pair);
}
}
return res;
}
std::string _cache_file;
plist::dictionary_t _cache;
std::set<std::string> _accessed_paths;
bool _dirty;
};
}
void item_t::traverse (std::map<std::string, fs::node_t> const& heads, std::string const& cacheFile)
{
property_cache_t plistCache(cacheFile);
std::vector<item_ptr> items(1, item_t::menu_item_separator());
std::map< oak::uuid_t, std::vector<oak::uuid_t> > menus;
std::map<oak::uuid_t, delta_item_t> deltaItems;
std::set< std::pair<oak::uuid_t, oak::uuid_t> > hiddenItems, deletedItems;
std::set<oak::uuid_t> loadedItems;
bool local = true;
iterate(path, locations())
{
std::string cwd = path::join(*path, "Bundles");
std::map<std::string, fs::node_t>::const_iterator node = heads.find(cwd);
if(node == heads.end() || node->second.type() == fs::node_t::kNodeTypeMissing)
{
local = false;
continue;
}
fs::node_t bundlesNode = resolve(cwd, node->second, heads);
if(bundlesNode.type() != fs::node_t::kNodeTypeDirectory || !bundlesNode.entries())
{
fprintf(stderr, "*** no bundles for path %s\n", bundlesNode.real_path(cwd).c_str());
continue;
}
citerate(fsBundle, *bundlesNode.entries())
{
if(text::lowercase(path::extension(fsBundle->name())) != ".tmbundle")
{
fprintf(stderr, "not a bundle: %s\n", fsBundle->name().c_str());
continue;
}
std::string bundlePath = fsBundle->real_path(cwd);
fs::node_t bundleNode = resolve(bundlePath, *fsBundle, heads);
if(bundleNode.type() != fs::node_t::kNodeTypeDirectory)
{
fprintf(stderr, "not a directory: %s (%s)\n", bundleNode.name().c_str(), bundleNode.path(cwd).c_str());
continue;
}
item_ptr bundle;
citerate(infoPlist, *bundleNode.entries())
{
if(infoPlist->name() != "info.plist")
continue;
std::string infoPlistPath = infoPlist->real_path(bundlePath);
fs::node_t infoPlistNode = resolve(infoPlistPath, *infoPlist, heads);
if(infoPlistNode.type() != fs::node_t::kNodeTypeFile)
break;
oak::uuid_t bundleUUID;
plist::dictionary_t plist = plistCache.get(infoPlistPath, infoPlistNode.modified());
if(!plist::get_key_path(plist, kFieldUUID, bundleUUID))
break;
bundle.reset(new item_t(bundleUUID, item_ptr(), kItemTypeBundle, local));
bundle->add_path(infoPlistPath);
bool isDelta = false;
if(plist::get_key_path(plist, kFieldIsDelta, isDelta) && isDelta)
{
deltaItems.insert(std::make_pair(bundleUUID, delta_item_t(bundle, plist)));
break;
}
std::map<oak::uuid_t, delta_item_t>::iterator deltaItem = deltaItems.find(bundleUUID);
if(deltaItem != deltaItems.end())
{
bundle = deltaItem->second.item;
bundle->add_path(infoPlistPath);
std::vector<plist::dictionary_t> plists;
plists.push_back(deltaItem->second.plist);
plists.push_back(plist);
plist = plist::merge_delta(plists);
deltaItems.erase(deltaItem);
}
else if(loadedItems.find(bundleUUID) != loadedItems.end())
{
// fprintf(stderr, "skip %s (already loaded)\n", infoPlistPath.c_str());
break;
}
bundle->initialize(plist);
items.push_back(bundle);
plist::array_t uuids;
plist::get_key_path(plist, kFieldDeletedItems, uuids);
iterate(uuid, uuids)
{
if(std::string const* str = boost::get<std::string>(&*uuid))
deletedItems.insert(std::make_pair(*str, bundleUUID));
}
// ====================
// = Load Bundle Menu =
// ====================
plist::array_t mainMenu;
plist::get_key_path(plist, "mainMenu.items", mainMenu);
menus.insert(std::make_pair(bundleUUID, to_menu(mainMenu)));
plist::dictionary_t subMenus;
plist::get_key_path(plist, "mainMenu.submenus", subMenus);
iterate(submenuIter, subMenus)
{
std::string name;
plist::array_t uuids;
if(plist::get_key_path(submenuIter->second, kFieldName, name) && plist::get_key_path(submenuIter->second, "items", uuids))
{
item_ptr item(new item_t(submenuIter->first, bundle, kItemTypeMenu));
item->_uuid = submenuIter->first;
item->_fields.insert(std::make_pair(kFieldName, name));
items.push_back(item);
menus.insert(std::make_pair(submenuIter->first, to_menu(uuids)));
}
}
plist::get_key_path(plist, "mainMenu.excludedItems", uuids);
iterate(uuid, uuids)
{
if(std::string const* str = boost::get<std::string>(&*uuid))
hiddenItems.insert(std::make_pair(*str, bundleUUID));
}
break;
}
if(!bundle)
{
fprintf(stderr, "no bundle: %s\n", bundlePath.c_str());
continue;
}
citerate(dir, *bundleNode.entries())
{
static struct { std::string name; std::string extension; kind_t kind; } const dirs[] =
{
{ "Commands", ".tmCommand", kItemTypeCommand },
{ "DragCommands", ".tmDragCommand", kItemTypeDragCommand },
{ "Macros", ".tmMacro", kItemTypeMacro },
{ "Preferences", ".tmPreferences", kItemTypeSettings },
{ "Snippets", ".tmSnippet", kItemTypeSnippet },
{ "Syntaxes", ".tmLanguage", kItemTypeGrammar },
{ "Proxies", ".tmProxy", kItemTypeProxy },
{ "Themes", ".tmTheme", kItemTypeTheme },
};
for(size_t i = 0; i < sizeofA(dirs); ++i)
{
if(dir->name() != dirs[i].name)
continue;
std::string dirPath = dir->real_path(bundlePath);
fs::node_t dirNode = resolve(dirPath, *dir, heads);
if(dirNode.type() != fs::node_t::kNodeTypeDirectory)
break;
std::string const extensions[] = { dirs[i].extension, ".tmDelta", ".plist" };
citerate(fsItem, *dirNode.entries())
{
if(!oak::contains(std::begin(extensions), std::end(extensions), path::extension(fsItem->name())))
{
fprintf(stderr, "wrong item type: %s\n", fsItem->name().c_str());
continue;
}
std::string itemPath = fsItem->real_path(dirPath);
fs::node_t itemNode = resolve(itemPath, *fsItem, heads);
if(itemNode.type() != fs::node_t::kNodeTypeFile)
continue;
plist::dictionary_t plist = plistCache.get(itemPath, itemNode.modified());
oak::uuid_t uuid;
if(!plist::get_key_path(plist, kFieldUUID, uuid))
continue;
if(loadedItems.find(uuid) != loadedItems.end())
{
// fprintf(stderr, "skip %s (already loaded)\n", itemPath.c_str());
continue;
}
item_ptr item(new item_t(uuid, bundle, dirs[i].kind, local));
item->add_path(itemPath);
bool isDelta = false;
if(plist::get_key_path(plist, kFieldIsDelta, isDelta) && isDelta)
{
deltaItems.insert(std::make_pair(uuid, delta_item_t(item, plist)));
continue;
}
std::map<oak::uuid_t, delta_item_t>::iterator deltaItem = deltaItems.find(uuid);
if(deltaItem != deltaItems.end())
{
item = deltaItem->second.item;
item->add_path(itemPath);
std::vector<plist::dictionary_t> plists;
plists.push_back(deltaItem->second.plist);
plists.push_back(plist);
plist = plist::merge_delta(plists);
deltaItems.erase(deltaItem);
}
else if(loadedItems.find(bundle->uuid()) != loadedItems.end())
{
// fprintf(stderr, "skip %s (bundle already loaded from more local path)\n", itemPath.c_str());
continue;
}
item->initialize(plist);
items.push_back(item);
loadedItems.insert(uuid);
}
break;
}
}
if(deltaItems.find(bundle->uuid()) == deltaItems.end())
loadedItems.insert(bundle->uuid());
}
local = false;
}
for(ssize_t i = items.size(); i-- > 0; )
{
item_ptr item = items[i];
if(deltaItems.find(item->bundle_uuid()) != deltaItems.end())
{
fprintf(stderr, "Warning: Bundle item %s at path %s has no (non-delta) parent\n", item->name().c_str(), text::join(item->paths(), ", ").c_str());
items.erase(items.begin() + i);
}
else
{
item->_hidden_from_user = hiddenItems.find(std::make_pair(item->uuid(), item->bundle_uuid())) != hiddenItems.end() ?: item->_hidden_from_user;
item->_deleted = deletedItems.find(std::make_pair(item->uuid(), item->bundle_uuid())) != deletedItems.end() ?: item->_deleted;
}
}
set_index(items, menus);
}
void build_index (std::string const& cacheDir)
{
static path::glob_t const dirGlob = "*.tm[Bb]undle{,/{Commands,DragCommands,Macros,Preferences,Proxies,Snippets,Syntaxes,Themes}}";
static path::glob_t const fileGlob = "*.tm[Bb]undle/{info.plist,Commands/*.{plist,tmCommand,tmDelta},DragCommands/*.{plist,tmDragCommand,tmDelta},Macros/*.{plist,tmMacro,tmDelta},Preferences/*.{plist,tmPreferences,tmDelta},Proxies/*.{tmProxy,tmDelta},Snippets/*.{plist,tmSnippet,tmDelta},Syntaxes/*.{plist,tmLanguage,tmDelta},Themes/*.{plist,tmTheme,tmDelta}}";
struct callback_t : fs::callback_t
{
callback_t (std::string const& cacheFile) : _cache_file(cacheFile) { }
void did_change (std::map<std::string, fs::node_t> const& heads, std::map< std::string, std::vector<std::string> > const& changes)
{
item_t::traverse(heads, _cache_file);
}
private:
std::string _cache_file;
};
std::set<std::string> rootPaths;
iterate(path, bundles::locations())
rootPaths.insert(path::join(*path, "Bundles"));
std::string const fsTreeCacheFile = path::join(cacheDir == NULL_STR ? "/tmp" : cacheDir, "FSNodes.plist");
std::string const plistCacheFile = path::join(cacheDir == NULL_STR ? "/tmp" : cacheDir, "PropertyValues.plist");
static fs::watch_info_ptr info = fs::watch_paths(rootPaths, new callback_t(plistCacheFile), dirGlob, fileGlob, fsTreeCacheFile);
}
} /* bundles */