From f888376e05148aea63b2c85006366c71f7074668 Mon Sep 17 00:00:00 2001 From: Allan Odgaard Date: Sun, 17 Mar 2013 16:02:30 +0100 Subject: [PATCH] Move bundle loading to own file --- Frameworks/bundles/src/index.cc | 420 +------------------------------ Frameworks/bundles/src/index.h | 5 + Frameworks/bundles/src/io.cc | 424 ++++++++++++++++++++++++++++++++ 3 files changed, 432 insertions(+), 417 deletions(-) create mode 100644 Frameworks/bundles/src/io.cc diff --git a/Frameworks/bundles/src/index.cc b/Frameworks/bundles/src/index.cc index 099b01d1..7dbe90da 100644 --- a/Frameworks/bundles/src/index.cc +++ b/Frameworks/bundles/src/index.cc @@ -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 -#include +#include #include -#include #include 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 const& heads) - { - while(node.type() == fs::node_t::kNodeTypeLink) - { - std::map::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 to_menu (plist::array_t const& uuids) - { - std::vector res; - iterate(uuid, uuids) - { - if(std::string const* str = boost::get(&*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 knownPaths, deadPaths; - std::transform(_cache.begin(), _cache.end(), back_inserter(knownPaths), [](std::pair 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(&cachedNode->second)) - { - plist::dictionary_t const& node = boost::get(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(&res->second)) - return boost::get(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 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(&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(&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 _accessed_paths; - bool _dirty; - }; - } - - void item_t::traverse (std::map const& heads, std::string const& cacheFile) - { - property_cache_t plistCache(cacheFile); - - std::vector items(1, item_t::menu_item_separator()); - std::map< oak::uuid_t, std::vector > menus; - - std::map deltaItems; - std::set< std::pair > hiddenItems, deletedItems; - std::set loadedItems; - - bool local = true; - iterate(path, locations()) - { - std::string cwd = path::join(*path, "Bundles"); - std::map::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::iterator deltaItem = deltaItems.find(bundleUUID); - if(deltaItem != deltaItems.end()) - { - bundle = deltaItem->second.item; - bundle->add_path(infoPlistPath); - - std::vector 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(&*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(&*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::iterator deltaItem = deltaItems.find(uuid); - if(deltaItem != deltaItems.end()) - { - item = deltaItem->second.item; - item->add_path(itemPath); - - std::vector 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 const& heads, std::map< std::string, std::vector > const& changes) - { - item_t::traverse(heads, _cache_file); - } - - private: - std::string _cache_file; - }; - - std::set 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 */ diff --git a/Frameworks/bundles/src/index.h b/Frameworks/bundles/src/index.h index e7442316..4a06de93 100644 --- a/Frameworks/bundles/src/index.h +++ b/Frameworks/bundles/src/index.h @@ -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_ptr; diff --git a/Frameworks/bundles/src/io.cc b/Frameworks/bundles/src/io.cc new file mode 100644 index 00000000..296ef729 --- /dev/null +++ b/Frameworks/bundles/src/io.cc @@ -0,0 +1,424 @@ +#include "index.h" +#include "locations.h" +#include "query.h" // set_index +#include "fsevents/fs_controller.h" +#include +#include +#include + +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 const& heads) + { + while(node.type() == fs::node_t::kNodeTypeLink) + { + std::map::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 to_menu (plist::array_t const& uuids) + { + std::vector res; + iterate(uuid, uuids) + { + if(std::string const* str = boost::get(&*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 knownPaths, deadPaths; + std::transform(_cache.begin(), _cache.end(), back_inserter(knownPaths), [](std::pair 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(&cachedNode->second)) + { + plist::dictionary_t const& node = boost::get(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(&res->second)) + return boost::get(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 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(&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(&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 _accessed_paths; + bool _dirty; + }; + } + + void item_t::traverse (std::map const& heads, std::string const& cacheFile) + { + property_cache_t plistCache(cacheFile); + + std::vector items(1, item_t::menu_item_separator()); + std::map< oak::uuid_t, std::vector > menus; + + std::map deltaItems; + std::set< std::pair > hiddenItems, deletedItems; + std::set loadedItems; + + bool local = true; + iterate(path, locations()) + { + std::string cwd = path::join(*path, "Bundles"); + std::map::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::iterator deltaItem = deltaItems.find(bundleUUID); + if(deltaItem != deltaItems.end()) + { + bundle = deltaItem->second.item; + bundle->add_path(infoPlistPath); + + std::vector 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(&*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(&*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::iterator deltaItem = deltaItems.find(uuid); + if(deltaItem != deltaItems.end()) + { + item = deltaItem->second.item; + item->add_path(itemPath); + + std::vector 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 const& heads, std::map< std::string, std::vector > const& changes) + { + item_t::traverse(heads, _cache_file); + } + + private: + std::string _cache_file; + }; + + std::set 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 */ \ No newline at end of file