mirror of
https://github.com/textmate/textmate.git
synced 2026-04-28 03:00:34 -04:00
Add new bundle loading code
Some shortcomings: 1. Cache format is more wasteful (and kept in memory) 2. Device UUID and inode of root folder is not considered when replaying fs-events (we should do a recursive rescan if either changes). On the bright side, the code better separates loading bundles from disk, and maintaining a disk cache updated via fs-events. This should make it easier to force cache invalidation when updating bundles from within TextMate (so we do not rely on fs-events) and to move bundle loading away from the main thread. Additionally, by moving the bundle loading code to the bundle manager singleton, we’re introducing a single source of knowledge about “bundles on disk”, regardless of wether they are managed or not.
This commit is contained in:
269
Frameworks/BundlesManager/src/fs_cache.cc
Normal file
269
Frameworks/BundlesManager/src/fs_cache.cc
Normal file
@@ -0,0 +1,269 @@
|
||||
#include "fs_cache.h"
|
||||
#include <io/entries.h>
|
||||
#include <text/format.h>
|
||||
#include <oak/debug.h>
|
||||
|
||||
OAK_DEBUG_VAR(Bundles_Cache);
|
||||
|
||||
static std::string read_link (std::string const& path)
|
||||
{
|
||||
D(DBF_Bundles_Cache, bug("%s\n", path.c_str()););
|
||||
|
||||
char buf[PATH_MAX];
|
||||
ssize_t len = readlink(path.c_str(), buf, sizeof(buf));
|
||||
if(0 < len && len < PATH_MAX)
|
||||
{
|
||||
return std::string(buf, buf + len);
|
||||
}
|
||||
else
|
||||
{
|
||||
std::string errStr = len == -1 ? strerror(errno) : text::format("Result outside allowed range %zd", len);
|
||||
fprintf(stderr, "*** readlink(‘%s’) failed: %s\n", path.c_str(), errStr.c_str());
|
||||
}
|
||||
return NULL_STR;
|
||||
}
|
||||
|
||||
namespace fs
|
||||
{
|
||||
int32_t const cache_t::kPropertyCacheFormatVersion = 1;
|
||||
|
||||
cache_t::cache_t (std::string const& path, plist::dictionary_t (*prune_dictionary)(plist::dictionary_t const&)) : _path(path), _prune_dictionary(prune_dictionary)
|
||||
{
|
||||
int32_t version;
|
||||
auto plist = plist::load(_path);
|
||||
if(plist::get_key_path(plist, "version", version) && version == kPropertyCacheFormatVersion)
|
||||
{
|
||||
for(auto pair : plist)
|
||||
{
|
||||
if(plist::dictionary_t const* node = boost::get<plist::dictionary_t>(&pair.second))
|
||||
{
|
||||
entry_type_t type = entry_type_t::directory;
|
||||
plist::dictionary_t content;
|
||||
std::string link = NULL_STR;
|
||||
bool flag;
|
||||
|
||||
if(plist::get_key_path(*node, "content", content))
|
||||
type = entry_type_t::file;
|
||||
else if(plist::get_key_path(*node, "link", link))
|
||||
type = entry_type_t::link;
|
||||
else if(plist::get_key_path(*node, "missing", flag) && flag)
|
||||
type = entry_type_t::missing;
|
||||
|
||||
entry_t entry(pair.first, type, link);
|
||||
entry.set_content(content);
|
||||
oak::date_t modified;
|
||||
if(plist::get_key_path(*node, "modified", modified))
|
||||
entry.set_modified(modified.time_value());
|
||||
|
||||
plist::array_t array;
|
||||
std::string globString;
|
||||
if(plist::get_key_path(*node, "entries", array) && plist::get_key_path(*node, "glob", globString))
|
||||
{
|
||||
std::vector<std::string> v;
|
||||
for(auto path : array)
|
||||
v.push_back(boost::get<std::string>(path));
|
||||
entry.set_entries(v, globString);
|
||||
}
|
||||
|
||||
uint64_t eventId;
|
||||
if(plist::get_key_path(*node, "eventId", eventId))
|
||||
entry.set_event_id(eventId);
|
||||
|
||||
_cache.emplace(pair.first, entry);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void cache_t::save () const
|
||||
{
|
||||
D(DBF_Bundles_Cache, bug("%s\n", _path.c_str()););
|
||||
|
||||
plist::dictionary_t plist;
|
||||
for(auto pair : _cache)
|
||||
{
|
||||
plist::dictionary_t node;
|
||||
|
||||
plist::array_t entries;
|
||||
for(auto path : pair.second.entries())
|
||||
entries.push_back(path);
|
||||
|
||||
if(pair.second.is_file())
|
||||
{
|
||||
node["content"] = pair.second.content();
|
||||
node["modified"] = oak::date_t(pair.second.modified());
|
||||
}
|
||||
else if(pair.second.is_link())
|
||||
node["link"] = pair.second.link();
|
||||
else if(pair.second.is_missing())
|
||||
node["missing"] = true;
|
||||
else if(pair.second.is_directory())
|
||||
{
|
||||
node["entries"] = entries;
|
||||
node["glob"] = pair.second.glob_string();
|
||||
if(pair.second.event_id())
|
||||
node["eventId"] = pair.second.event_id();
|
||||
}
|
||||
|
||||
plist.emplace(pair.first, node);
|
||||
}
|
||||
plist["version"] = kPropertyCacheFormatVersion;
|
||||
plist::save(_path, plist);
|
||||
}
|
||||
|
||||
uint64_t cache_t::event_id_for_path (std::string const& path) const
|
||||
{
|
||||
auto it = _cache.find(path);
|
||||
return it == _cache.end() ? 0 : it->second.event_id();
|
||||
}
|
||||
|
||||
void cache_t::set_event_id_for_path (uint64_t eventId, std::string const& path)
|
||||
{
|
||||
auto it = _cache.find(path);
|
||||
if(it != _cache.end() && it->second.event_id() != eventId)
|
||||
{
|
||||
it->second.set_event_id(eventId);
|
||||
_dirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
plist::dictionary_t cache_t::content (std::string const& path)
|
||||
{
|
||||
return resolved(path).content();
|
||||
}
|
||||
|
||||
std::vector<std::string> cache_t::entries (std::string const& path, std::string const& globString)
|
||||
{
|
||||
entry_t& entry = resolved(path, globString);
|
||||
|
||||
std::vector<std::string> res;
|
||||
for(auto path : entry.entries())
|
||||
res.emplace_back(path::join(entry.path(), path));
|
||||
return res;
|
||||
}
|
||||
|
||||
bool cache_t::erase (std::string const& path)
|
||||
{
|
||||
auto it = _cache.find(path);
|
||||
if(it == _cache.end())
|
||||
return false;
|
||||
_cache.erase(it);
|
||||
_dirty = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool cache_t::reload (std::string const& path)
|
||||
{
|
||||
bool dirty = false;
|
||||
auto it = _cache.find(path);
|
||||
if(it == _cache.end())
|
||||
{
|
||||
D(DBF_Bundles_Cache, bug("no entry for ‘%s’\n", path.c_str()););
|
||||
return dirty;
|
||||
}
|
||||
|
||||
struct stat buf;
|
||||
D(DBF_Bundles_Cache, bug("lstat ‘%s’\n", path.c_str()););
|
||||
if(lstat(path.c_str(), &buf) == 0)
|
||||
{
|
||||
if(S_ISDIR(buf.st_mode) && it->second.is_directory())
|
||||
{
|
||||
auto oldEntries = it->second.entries();
|
||||
update_entries(it->second, it->second.glob_string());
|
||||
auto newEntries = it->second.entries();
|
||||
dirty = oldEntries != newEntries;
|
||||
D(DBF_Bundles_Cache, bug("entries changed ‘%s’\n", BSTR(oldEntries != newEntries)););
|
||||
for(auto name : newEntries)
|
||||
{
|
||||
auto entryIter = _cache.find(path::join(path, name));
|
||||
if(entryIter != _cache.end() && entryIter->second.is_file())
|
||||
dirty = reload(path::join(path, name)) || dirty;
|
||||
}
|
||||
}
|
||||
else if(!(it->second.is_file() && S_ISREG(buf.st_mode) && it->second.modified() == buf.st_mtimespec.tv_sec))
|
||||
{
|
||||
D(DBF_Bundles_Cache, bug("erase ‘%s’ (path changed)\n", path.c_str()););
|
||||
_cache.erase(it);
|
||||
dirty = true;
|
||||
}
|
||||
}
|
||||
else if(!it->second.is_missing())
|
||||
{
|
||||
D(DBF_Bundles_Cache, bug("erase ‘%s’ (path missing)\n", path.c_str()););
|
||||
_cache.erase(it);
|
||||
dirty = true;
|
||||
}
|
||||
|
||||
_dirty = _dirty || dirty;
|
||||
return dirty;
|
||||
}
|
||||
|
||||
bool cache_t::cleanup (std::vector<std::string> const& rootPaths)
|
||||
{
|
||||
std::set<std::string> allPaths, reachablePaths;
|
||||
std::transform(_cache.begin(), _cache.end(), std::inserter(allPaths, allPaths.end()), [](std::pair<std::string, entry_t> const& pair){ return pair.first; });
|
||||
for(auto path : rootPaths)
|
||||
copy_all(path, std::inserter(reachablePaths, reachablePaths.end()));
|
||||
|
||||
std::vector<std::string> toRemove;
|
||||
std::set_difference(allPaths.begin(), allPaths.end(), reachablePaths.begin(), reachablePaths.end(), back_inserter(toRemove));
|
||||
|
||||
D(DBF_Bundles_Cache, if(!toRemove.empty()) bug("drop:\n - %s\n", text::join(toRemove, "\n - ").c_str()););
|
||||
for(auto path : toRemove)
|
||||
_cache.erase(path);
|
||||
_dirty = _dirty || !toRemove.empty();
|
||||
return !toRemove.empty();
|
||||
}
|
||||
|
||||
// ============================
|
||||
// = Private Member Functions =
|
||||
// ============================
|
||||
|
||||
cache_t::entry_t& cache_t::resolved (std::string const& path, std::string const& globString)
|
||||
{
|
||||
auto it = _cache.find(path);
|
||||
if(it == _cache.end())
|
||||
{
|
||||
entry_type_t type = entry_type_t::missing;
|
||||
|
||||
struct stat buf;
|
||||
D(DBF_Bundles_Cache, bug("lstat ‘%s’\n", path.c_str()););
|
||||
if(lstat(path.c_str(), &buf) == 0)
|
||||
{
|
||||
if(S_ISREG(buf.st_mode))
|
||||
type = entry_type_t::file;
|
||||
else if(S_ISLNK(buf.st_mode))
|
||||
type = entry_type_t::link;
|
||||
else if(S_ISDIR(buf.st_mode))
|
||||
type = entry_type_t::directory;
|
||||
}
|
||||
|
||||
entry_t entry(path, type, type == entry_type_t::link ? read_link(path) : NULL_STR);
|
||||
if(entry.is_file())
|
||||
{
|
||||
D(DBF_Bundles_Cache, bug("load ‘%s’\n", path.c_str()););
|
||||
entry.set_content(_prune_dictionary(plist::load(path)));
|
||||
entry.set_modified(buf.st_mtimespec.tv_sec);
|
||||
}
|
||||
else if(entry.is_directory())
|
||||
{
|
||||
update_entries(entry, globString);
|
||||
}
|
||||
|
||||
it = _cache.emplace(path, entry).first;
|
||||
_dirty = true;
|
||||
}
|
||||
return it->second.is_link() ? resolved(it->second.resolved(), globString) : it->second;
|
||||
}
|
||||
|
||||
void cache_t::update_entries (entry_t& entry, std::string const& globString)
|
||||
{
|
||||
D(DBF_Bundles_Cache, bug("scan-dir ‘%s’\n", entry.path().c_str()););
|
||||
std::vector<std::string> entries;
|
||||
for(auto dirEntry : path::entries(entry.path(), globString))
|
||||
entries.emplace_back(dirEntry->d_name);
|
||||
std::sort(entries.begin(), entries.end());
|
||||
entry.set_entries(entries, globString);
|
||||
}
|
||||
|
||||
} /* fs */
|
||||
126
Frameworks/BundlesManager/src/fs_cache.h
Normal file
126
Frameworks/BundlesManager/src/fs_cache.h
Normal file
@@ -0,0 +1,126 @@
|
||||
#ifndef FS_CACHE_H_3ILT90EK
|
||||
#define FS_CACHE_H_3ILT90EK
|
||||
|
||||
#include <io/path.h>
|
||||
#include <plist/plist.h>
|
||||
|
||||
namespace fs
|
||||
{
|
||||
struct cache_t
|
||||
{
|
||||
cache_t (std::string const& path, plist::dictionary_t (*prune_dictionary)(plist::dictionary_t const&));
|
||||
void save () const;
|
||||
|
||||
bool dirty () const { return _dirty; }
|
||||
void set_dirty (bool flag) { _dirty = flag; }
|
||||
|
||||
uint64_t event_id_for_path (std::string const& path) const;
|
||||
void set_event_id_for_path (uint64_t eventId, std::string const& path);
|
||||
|
||||
plist::dictionary_t content (std::string const& path);
|
||||
std::vector<std::string> entries (std::string const& path, std::string const& globString = NULL_STR);
|
||||
|
||||
bool erase (std::string const& path);
|
||||
bool reload (std::string const& path);
|
||||
bool cleanup (std::vector<std::string> const& rootPaths);
|
||||
|
||||
template <typename _OutputIter>
|
||||
_OutputIter copy_heads_for_path (std::string const& path, _OutputIter out)
|
||||
{
|
||||
*out++ = path;
|
||||
return copy_links(_cache.find(path), out);
|
||||
}
|
||||
|
||||
private:
|
||||
static int32_t const kPropertyCacheFormatVersion;
|
||||
enum class entry_type_t { file, directory, link, missing };
|
||||
|
||||
struct entry_t
|
||||
{
|
||||
entry_t (std::string const& path, entry_type_t type, std::string const& link) : _path(path), _type(type), _link(link) { }
|
||||
|
||||
bool is_link () const { return _type == entry_type_t::link; }
|
||||
bool is_file () const { return _type == entry_type_t::file; }
|
||||
bool is_directory () const { return _type == entry_type_t::directory; }
|
||||
bool is_missing () const { return _type == entry_type_t::missing; }
|
||||
|
||||
entry_type_t type () const { return _type; }
|
||||
std::string const& path () const { return _path; }
|
||||
std::string const& link () const { return _link; }
|
||||
std::string resolved () const { return path::join(path::parent(_path), _link); }
|
||||
time_t modified () const { return _modified; }
|
||||
uint64_t event_id () const { return _event_id; }
|
||||
plist::dictionary_t const& content () const { return _content; }
|
||||
std::vector<std::string> const& entries () const { return _entries; }
|
||||
std::string glob_string () const { return _glob_string; }
|
||||
|
||||
void set_type (entry_type_t type) { _type = type; }
|
||||
void set_link (std::string const& link) { _link = link; }
|
||||
void set_modified (time_t modified) { _modified = modified; }
|
||||
void set_event_id (uint64_t eventId) { _event_id = eventId; }
|
||||
void set_content (plist::dictionary_t const& plist) { _content = plist; }
|
||||
void set_entries (std::vector<std::string> const& array, std::string const& globString) { _entries = array; _glob_string = globString; }
|
||||
|
||||
private:
|
||||
std::string _path;
|
||||
entry_type_t _type;
|
||||
std::string _link;
|
||||
std::string _glob_string;
|
||||
time_t _modified;
|
||||
uint64_t _event_id = 0;
|
||||
plist::dictionary_t _content;
|
||||
std::vector<std::string> _entries;
|
||||
};
|
||||
|
||||
std::string _path;
|
||||
plist::dictionary_t (*_prune_dictionary)(plist::dictionary_t const&);
|
||||
std::map<std::string, entry_t> _cache;
|
||||
bool _dirty = false;
|
||||
|
||||
entry_t& resolved (std::string const& path, std::string const& globString = NULL_STR);
|
||||
static void update_entries (entry_t& entry, std::string const& globString);
|
||||
|
||||
template <typename _InputIter, typename _OutputIter>
|
||||
_OutputIter copy_links (_InputIter entryIter, _OutputIter out)
|
||||
{
|
||||
if(entryIter == _cache.end())
|
||||
return out;
|
||||
|
||||
entry_t const& entry = entryIter->second;
|
||||
if(entry.is_link())
|
||||
{
|
||||
*out++ = entry.resolved();
|
||||
out = copy_links(_cache.find(entry.resolved()), out);
|
||||
}
|
||||
else if(entry.is_directory())
|
||||
{
|
||||
for(auto path : entries(entry.path(), entry.glob_string()))
|
||||
out = copy_links(_cache.find(path), out);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
template <typename _OutputIter>
|
||||
_OutputIter copy_all (std::string const& path, _OutputIter out)
|
||||
{
|
||||
auto it = _cache.find(path);
|
||||
if(it != _cache.end())
|
||||
{
|
||||
*out++ = path;
|
||||
if(it->second.is_directory())
|
||||
{
|
||||
for(auto child : entries(it->second.path(), it->second.glob_string()))
|
||||
out = copy_all(child, out);
|
||||
}
|
||||
else if(it->second.is_link())
|
||||
{
|
||||
out = copy_all(it->second.resolved(), out);
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
};
|
||||
|
||||
} /* fs */
|
||||
|
||||
#endif /* end of include guard: FS_CACHE_H_3ILT90EK */
|
||||
369
Frameworks/BundlesManager/src/load_bundles.cc
Normal file
369
Frameworks/BundlesManager/src/load_bundles.cc
Normal file
@@ -0,0 +1,369 @@
|
||||
#include "load_bundles.h"
|
||||
#include "fs_cache.h"
|
||||
#include <bundles/locations.h>
|
||||
#include <bundles/index.h>
|
||||
#include <bundles/query.h> // set_index
|
||||
#include <plist/delta.h>
|
||||
#include <regexp/glob.h>
|
||||
#include <text/format.h>
|
||||
#include <io/events.h>
|
||||
#include <oak/debug.h>
|
||||
|
||||
OAK_DEBUG_VAR(Bundles_Load);
|
||||
OAK_DEBUG_VAR(Bundles_FSEvents);
|
||||
|
||||
namespace
|
||||
{
|
||||
static std::string const kFieldChangedItems = "changed";
|
||||
static std::string const kFieldDeletedItems = "deleted";
|
||||
static std::string const kFieldMainMenu = "mainMenu";
|
||||
static std::string const kSeparatorString = "------------------------------------";
|
||||
|
||||
static plist::dictionary_t prune_dictionary (plist::dictionary_t const& plist)
|
||||
{
|
||||
static auto const DesiredKeys = new std::set<std::string>{ bundles::kFieldName, bundles::kFieldKeyEquivalent, bundles::kFieldTabTrigger, bundles::kFieldScopeSelector, bundles::kFieldSemanticClass, bundles::kFieldContentMatch, bundles::kFieldGrammarFirstLineMatch, bundles::kFieldGrammarScope, bundles::kFieldGrammarInjectionSelector, bundles::kFieldDropExtension, bundles::kFieldGrammarExtension, bundles::kFieldSettingName, bundles::kFieldHideFromUser, bundles::kFieldIsDeleted, bundles::kFieldIsDisabled, bundles::kFieldRequiredItems, bundles::kFieldUUID, bundles::kFieldIsDelta, kFieldMainMenu, kFieldDeletedItems, kFieldChangedItems };
|
||||
|
||||
plist::dictionary_t res;
|
||||
for(auto pair : plist)
|
||||
{
|
||||
if(DesiredKeys->find(pair.first) == DesiredKeys->end() && pair.first.find(bundles::kFieldSettingName) != 0)
|
||||
continue;
|
||||
|
||||
if(pair.first == bundles::kFieldSettingName)
|
||||
{
|
||||
if(plist::dictionary_t const* dictionary = boost::get<plist::dictionary_t>(&pair.second))
|
||||
{
|
||||
plist::array_t settings;
|
||||
iterate(settingsPair, *dictionary)
|
||||
settings.push_back(settingsPair->first);
|
||||
res.insert(std::make_pair(pair.first, settings));
|
||||
}
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
// ===================
|
||||
// = Index Functions =
|
||||
// ===================
|
||||
|
||||
static std::vector<oak::uuid_t> to_menu (plist::array_t const& uuids, std::string const& path)
|
||||
{
|
||||
std::vector<oak::uuid_t> res;
|
||||
for(auto uuid : uuids)
|
||||
{
|
||||
std::string const* str = boost::get<std::string>(&uuid);
|
||||
if(str && oak::uuid_t::is_valid(*str))
|
||||
res.push_back(*str == kSeparatorString ? bundles::kSeparatorUUID : oak::uuid_t(*str));
|
||||
else fprintf(stderr, "*** invalid uuid (%s) in ‘%s’\n", to_s(uuid).c_str(), path.c_str());
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
static void traverse (std::vector<std::string> const& bundlesPaths, fs::cache_t& cache)
|
||||
{
|
||||
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;
|
||||
};
|
||||
|
||||
std::vector<bundles::item_ptr> items(1, bundles::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<oak::uuid_t> loadedItems;
|
||||
|
||||
bool local = true;
|
||||
for(auto bundlesPath : bundlesPaths)
|
||||
{
|
||||
for(auto bundlePath : cache.entries(bundlesPath, "*.tm[Bb]undle"))
|
||||
{
|
||||
bundles::item_ptr bundle;
|
||||
std::set<oak::uuid_t> hiddenItems;
|
||||
bool skipEclipsedBundle = false;
|
||||
|
||||
auto entries = cache.entries(bundlePath, "{info.plist,Commands,DragCommands,Macros,Preferences,Proxies,Snippets,Syntaxes,Themes}");
|
||||
for(auto infoPlistPath : entries)
|
||||
{
|
||||
if(path::name(infoPlistPath) != "info.plist")
|
||||
continue;
|
||||
|
||||
oak::uuid_t bundleUUID;
|
||||
plist::dictionary_t plist = cache.content(infoPlistPath);
|
||||
if(!plist::get_key_path(plist, bundles::kFieldUUID, bundleUUID))
|
||||
{
|
||||
fprintf(stderr, "*** skip ‘%s’ (no valid UUID)\n", infoPlistPath.c_str());
|
||||
break;
|
||||
}
|
||||
|
||||
bundle.reset(new bundles::item_t(bundleUUID, bundles::item_ptr(), bundles::kItemTypeBundle, local));
|
||||
bundle->add_path(infoPlistPath);
|
||||
|
||||
bool isDelta = false;
|
||||
if(plist::get_key_path(plist, bundles::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())
|
||||
{
|
||||
D(DBF_Bundles_Load, bug("skip ‘%s’ (eclipsed by local bundle)\n", infoPlistPath.c_str()););
|
||||
bundle.reset();
|
||||
skipEclipsedBundle = true;
|
||||
break;
|
||||
}
|
||||
|
||||
bundle->initialize(plist);
|
||||
items.push_back(bundle);
|
||||
|
||||
// ====================
|
||||
// = Load Bundle Menu =
|
||||
// ====================
|
||||
|
||||
plist::array_t mainMenu;
|
||||
plist::get_key_path(plist, "mainMenu.items", mainMenu);
|
||||
menus.insert(std::make_pair(bundleUUID, to_menu(mainMenu, infoPlistPath)));
|
||||
|
||||
plist::dictionary_t subMenus;
|
||||
plist::get_key_path(plist, "mainMenu.submenus", subMenus);
|
||||
for(auto submenuIter : subMenus)
|
||||
{
|
||||
std::string name;
|
||||
plist::array_t uuids;
|
||||
if(oak::uuid_t::is_valid(submenuIter.first) && plist::get_key_path(submenuIter.second, bundles::kFieldName, name) && plist::get_key_path(submenuIter.second, "items", uuids))
|
||||
{
|
||||
bundles::item_ptr item(new bundles::item_t(submenuIter.first, bundle, bundles::kItemTypeMenu));
|
||||
item->set_name(name);
|
||||
items.push_back(item);
|
||||
menus.insert(std::make_pair(submenuIter.first, to_menu(uuids, infoPlistPath)));
|
||||
}
|
||||
else
|
||||
{
|
||||
fprintf(stderr, "*** invalid uuid (\"%s\") in ‘%s’\n", submenuIter.first.c_str(), infoPlistPath.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
plist::array_t uuids;
|
||||
plist::get_key_path(plist, "mainMenu.excludedItems", uuids);
|
||||
for(auto uuid : uuids)
|
||||
{
|
||||
std::string const* str = boost::get<std::string>(&uuid);
|
||||
if(str && oak::uuid_t::is_valid(*str))
|
||||
hiddenItems.insert(*str);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
if(!bundle)
|
||||
{
|
||||
if(!skipEclipsedBundle)
|
||||
fprintf(stderr, "*** not a bundle at ‘%s’\n", bundlePath.c_str());
|
||||
continue;
|
||||
}
|
||||
|
||||
for(auto dirPath : entries)
|
||||
{
|
||||
static struct { std::string name; std::string glob; bundles::kind_t kind; } const dirs[] =
|
||||
{
|
||||
{ "Commands", "*.{plist,tmDelta,tmCommand}", bundles::kItemTypeCommand },
|
||||
{ "DragCommands", "*.{plist,tmDelta,tmDragCommand}", bundles::kItemTypeDragCommand },
|
||||
{ "Macros", "*.{plist,tmDelta,tmMacro}", bundles::kItemTypeMacro },
|
||||
{ "Preferences", "*.{plist,tmDelta,tmPreferences}", bundles::kItemTypeSettings },
|
||||
{ "Snippets", "*.{plist,tmDelta,tmSnippet}", bundles::kItemTypeSnippet },
|
||||
{ "Syntaxes", "*.{plist,tmDelta,tmLanguage}", bundles::kItemTypeGrammar },
|
||||
{ "Proxies", "*.{plist,tmDelta,tmProxy}", bundles::kItemTypeProxy },
|
||||
{ "Themes", "*.{plist,tmDelta,tmTheme}", bundles::kItemTypeTheme },
|
||||
};
|
||||
|
||||
for(size_t i = 0; i < sizeofA(dirs); ++i)
|
||||
{
|
||||
if(path::name(dirPath) != dirs[i].name)
|
||||
continue;
|
||||
|
||||
for(auto itemPath : cache.entries(dirPath, dirs[i].glob))
|
||||
{
|
||||
oak::uuid_t uuid;
|
||||
plist::dictionary_t plist = cache.content(itemPath);
|
||||
if(!plist::get_key_path(plist, bundles::kFieldUUID, uuid))
|
||||
{
|
||||
fprintf(stderr, "*** skip ‘%s’ (no valid UUID)\n", itemPath.c_str());
|
||||
continue;
|
||||
}
|
||||
|
||||
if(loadedItems.find(uuid) != loadedItems.end())
|
||||
{
|
||||
fprintf(stderr, "*** skip ‘%s’ (item with same UUID already loaded)\n", itemPath.c_str());
|
||||
continue;
|
||||
}
|
||||
|
||||
bundles::item_ptr item(new bundles::item_t(uuid, bundle, dirs[i].kind, local));
|
||||
item->add_path(itemPath);
|
||||
|
||||
bool isDelta = false;
|
||||
if(plist::get_key_path(plist, bundles::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);
|
||||
}
|
||||
|
||||
if(hiddenItems.find(item->uuid()) != hiddenItems.end())
|
||||
plist.insert(std::make_pair(bundles::kFieldHideFromUser, true));
|
||||
|
||||
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; )
|
||||
{
|
||||
bundles::item_ptr item = items[i];
|
||||
if(deltaItems.find(item->bundle_uuid()) != deltaItems.end())
|
||||
{
|
||||
fprintf(stderr, "*** orphaned delta (‘%s’) at: %s\n", item->name().c_str(), text::join(item->paths(), ", ").c_str());
|
||||
items.erase(items.begin() + i);
|
||||
}
|
||||
}
|
||||
|
||||
set_index(items, menus);
|
||||
}
|
||||
}
|
||||
|
||||
void load_bundles (std::string const& cacheDir)
|
||||
{
|
||||
struct callback_t : fs::event_callback_t
|
||||
{
|
||||
fs::cache_t cache;
|
||||
std::vector<std::string> bundles_paths;
|
||||
std::set<std::string> watch_list;
|
||||
|
||||
callback_t (std::string const& cacheFile) : cache(cacheFile, &prune_dictionary)
|
||||
{
|
||||
for(auto path : bundles::locations())
|
||||
bundles_paths.push_back(path::join(path, "Bundles"));
|
||||
}
|
||||
|
||||
void set_replaying_history (bool flag, std::string const& observedPath, uint64_t eventId)
|
||||
{
|
||||
D(DBF_Bundles_FSEvents, bug("%s (observing ‘%s’)\n", BSTR(flag), observedPath.c_str()););
|
||||
cache.set_event_id_for_path(eventId, observedPath);
|
||||
}
|
||||
|
||||
void did_change (std::string const& path, std::string const& observedPath, uint64_t eventId, bool recursive)
|
||||
{
|
||||
D(DBF_Bundles_FSEvents, bug("%s (observing ‘%s’)\n", path.c_str(), observedPath.c_str()););
|
||||
|
||||
if(cache.reload(path))
|
||||
traverse(bundles_paths, cache);
|
||||
else D(DBF_Bundles_FSEvents, bug("no changes\n"););
|
||||
|
||||
cache.set_event_id_for_path(eventId, observedPath);
|
||||
save_cache();
|
||||
|
||||
// =====================
|
||||
// = Update watch list =
|
||||
// =====================
|
||||
|
||||
std::set<std::string> newWatchlist;
|
||||
for(auto path : bundles_paths)
|
||||
cache.copy_heads_for_path(path, std::inserter(newWatchlist, newWatchlist.end()));
|
||||
|
||||
std::vector<std::string> pathsAdded, pathsRemoved;
|
||||
std::set_difference(watch_list.begin(), watch_list.end(), newWatchlist.begin(), newWatchlist.end(), back_inserter(pathsRemoved));
|
||||
std::set_difference(newWatchlist.begin(), newWatchlist.end(), watch_list.begin(), watch_list.end(), back_inserter(pathsAdded));
|
||||
|
||||
watch_list.swap(newWatchlist);
|
||||
|
||||
for(auto path : pathsRemoved)
|
||||
{
|
||||
D(DBF_Bundles_FSEvents, bug("unwatch ‘%s’\n", path.c_str()););
|
||||
fs::unwatch(path, this);
|
||||
}
|
||||
|
||||
for(auto path : pathsAdded)
|
||||
{
|
||||
D(DBF_Bundles_FSEvents, bug("watch ‘%s’\n", path.c_str()););
|
||||
fs::watch(path, this, cache.event_id_for_path(path) ?: FSEventsGetCurrentEventId(), 1);
|
||||
}
|
||||
}
|
||||
|
||||
void load_bundles ()
|
||||
{
|
||||
traverse(bundles_paths, cache);
|
||||
|
||||
for(auto path : bundles_paths)
|
||||
cache.copy_heads_for_path(path, std::inserter(watch_list, watch_list.end()));
|
||||
|
||||
D(DBF_Bundles_FSEvents, bug("watch:\n - %s\n", text::join(watch_list, "\n - ").c_str()););
|
||||
|
||||
auto tmp = watch_list;
|
||||
for(auto path : tmp)
|
||||
fs::watch(path, this, cache.event_id_for_path(path) ?: FSEventsGetCurrentEventId(), 1);
|
||||
|
||||
save_cache();
|
||||
}
|
||||
|
||||
void save_cache ()
|
||||
{
|
||||
cache.cleanup(bundles_paths);
|
||||
if(cache.dirty())
|
||||
{
|
||||
cache.save();
|
||||
cache.set_dirty(false);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
callback_t* cb = new callback_t(path::join(cacheDir, "BundlesIndex.plist"));
|
||||
cb->load_bundles();
|
||||
}
|
||||
8
Frameworks/BundlesManager/src/load_bundles.h
Normal file
8
Frameworks/BundlesManager/src/load_bundles.h
Normal file
@@ -0,0 +1,8 @@
|
||||
#ifndef LOAD_BUNDLES_H_C8BVI372
|
||||
#define LOAD_BUNDLES_H_C8BVI372
|
||||
|
||||
#include <oak/misc.h>
|
||||
|
||||
PUBLIC void load_bundles (std::string const& cacheDir);
|
||||
|
||||
#endif /* end of include guard: LOAD_BUNDLES_H_C8BVI372 */
|
||||
@@ -1,4 +1,4 @@
|
||||
SOURCES = src/*.mm
|
||||
LINK += updater network OakFoundation
|
||||
SOURCES = src/*.{cc,mm}
|
||||
LINK += io text plist regexp bundles updater network OakFoundation
|
||||
EXPORT = src/BundlesManager.h
|
||||
FRAMEWORKS = Foundation
|
||||
|
||||
Reference in New Issue
Block a user