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:
Allan Odgaard
2013-03-25 11:18:27 +01:00
parent 950cb8b27a
commit 14ff3cf047
5 changed files with 774 additions and 2 deletions

View 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 */

View 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 */

View 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();
}

View 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 */

View File

@@ -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