mirror of
https://github.com/textmate/textmate.git
synced 2026-04-28 03:00:34 -04:00
Rework bundle install code
We now use dispatch queues for updating bundles and bundle sources. The determinate progress indicator is presently gone and so is the detailed info about which dependent bundles are being installed.
This commit is contained in:
@@ -1,126 +1,31 @@
|
||||
#import "BundlesManager.h"
|
||||
#import "load_bundles.h"
|
||||
#import <OakFoundation/NSDate Additions.h>
|
||||
#import <network/network.h>
|
||||
#import <oak/server.h>
|
||||
#import <OakFoundation/NSString Additions.h>
|
||||
#import <io/path.h>
|
||||
#import <oak/debug.h>
|
||||
|
||||
OAK_DEBUG_VAR(BundlesManager);
|
||||
|
||||
NSString* const kUserDefaultsDisableBundleUpdatesKey = @"disableBundleUpdates";
|
||||
NSString* const kUserDefaultsLastBundleUpdateCheckKey = @"lastBundleUpdateCheck";
|
||||
NSString* const BundlesManagerBundlesDidChangeNotification = @"BundlesManagerBundlesDidChangeNotification";
|
||||
|
||||
static std::string const kInstallDirectory = NULL_STR;
|
||||
static double const kPollInterval = 3*60*60;
|
||||
|
||||
@interface BundlesManager ()
|
||||
{
|
||||
std::vector<bundles_db::source_ptr> sourceList;
|
||||
std::vector<bundles_db::bundle_ptr> bundlesIndex;
|
||||
|
||||
NSUInteger scheduledTasks;
|
||||
std::set<oak::uuid_t> installing;
|
||||
}
|
||||
@property (nonatomic) NSTimer* updateTimer;
|
||||
@property (nonatomic) BOOL isBusy;
|
||||
@property (nonatomic) NSString* activityText;
|
||||
@property (nonatomic) double progress;
|
||||
@property (atomic) NSString* threadActivityText;
|
||||
@property (nonatomic) double threadProgress;
|
||||
@property (nonatomic) NSTimer* progressTimer;
|
||||
|
||||
- (void)didStartThreadActivity:(id)sender;
|
||||
- (void)didFinishActivityForSources:(std::vector<bundles_db::source_ptr> const&)someSources bundles:(std::vector<bundles_db::bundle_ptr> const&)someBundles;
|
||||
|
||||
- (void)updateSources:(id)sender;
|
||||
- (void)updateBundles:(id)sender;
|
||||
@property (nonatomic) NSTimer* updateTimer;
|
||||
@end
|
||||
|
||||
namespace
|
||||
{
|
||||
struct background_task_t
|
||||
{
|
||||
struct request_t
|
||||
{
|
||||
request_t (std::vector<bundles_db::source_ptr> const& sources, std::vector<bundles_db::bundle_ptr> const& bundles, BundlesManager* bundlesManager, double* threadProgress) : sources(sources), bundles(bundles), bundles_manager(bundlesManager), thread_progress(threadProgress) { }
|
||||
|
||||
std::vector<bundles_db::source_ptr> sources;
|
||||
std::vector<bundles_db::bundle_ptr> bundles;
|
||||
BundlesManager* bundles_manager;
|
||||
double* thread_progress;
|
||||
};
|
||||
|
||||
background_task_t (std::vector<bundles_db::source_ptr> const& sources, std::vector<bundles_db::bundle_ptr> const& bundles, BundlesManager* bundlesManager, double* threadProgress);
|
||||
~background_task_t ();
|
||||
static bool handle_request (request_t const& request);
|
||||
void handle_reply (bool result);
|
||||
|
||||
private:
|
||||
std::vector<bundles_db::source_ptr> _sources;
|
||||
std::vector<bundles_db::bundle_ptr> _bundles;
|
||||
BundlesManager* _self;
|
||||
size_t _client_key;
|
||||
};
|
||||
|
||||
static oak::server_t<background_task_t, background_task_t::request_t, bool>& server ()
|
||||
{
|
||||
static oak::server_t<background_task_t, background_task_t::request_t, bool> server;
|
||||
return server;
|
||||
}
|
||||
|
||||
background_task_t::background_task_t (std::vector<bundles_db::source_ptr> const& sources, std::vector<bundles_db::bundle_ptr> const& bundles, BundlesManager* bundlesManager, double* threadProgress) : _sources(sources), _bundles(bundles), _self(bundlesManager)
|
||||
{
|
||||
_client_key = server().register_client(this);
|
||||
[_self didStartThreadActivity:nil];
|
||||
server().send_request(_client_key, request_t(sources, bundles, bundlesManager, threadProgress));
|
||||
}
|
||||
|
||||
background_task_t::~background_task_t ()
|
||||
{
|
||||
server().unregister_client(_client_key);
|
||||
[_self didFinishActivityForSources:_sources bundles:_bundles];
|
||||
}
|
||||
|
||||
bool background_task_t::handle_request (request_t const& request)
|
||||
{
|
||||
@autoreleasepool {
|
||||
std::vector<bundles_db::source_ptr> const& sources = request.sources;
|
||||
std::vector<bundles_db::bundle_ptr> const& bundles = request.bundles;
|
||||
BundlesManager* self = request.bundles_manager;
|
||||
|
||||
for(size_t i = 0; i < sources.size(); ++i)
|
||||
{
|
||||
self.threadProgress = i / (double)sources.size();
|
||||
self.threadActivityText = [NSString stringWithFormat:@"Updating ‘%s’…", sources[i]->name().c_str()];
|
||||
bundles_db::update(sources[i], request.thread_progress, i / (double)sources.size(), (i + 1) / (double)sources.size());
|
||||
}
|
||||
|
||||
double totalSize = 0;
|
||||
iterate(bundle, bundles)
|
||||
totalSize += (*bundle)->size();
|
||||
|
||||
for(size_t size = 0, i = 0; i < bundles.size(); ++i)
|
||||
{
|
||||
self.threadProgress = size / totalSize;
|
||||
self.threadActivityText = [NSString stringWithFormat:@"%@Installing ‘%s’…", (bundles.size() > 1 ? [NSString stringWithFormat:@"%zu/%zu: ", i+1, bundles.size()] : @""), bundles[i]->name().c_str()];
|
||||
bundles_db::update(bundles[i], kInstallDirectory, request.thread_progress, size / totalSize, (size + bundles[i]->size()) / totalSize);
|
||||
size += bundles[i]->size();
|
||||
}
|
||||
|
||||
self.threadProgress = 1;
|
||||
if(bundles.size() == 0)
|
||||
self.threadActivityText = @"";
|
||||
else if(bundles.size() == 1)
|
||||
self.threadActivityText = [NSString stringWithFormat:@"Installed ‘%s’.", bundles.back()->name().c_str()];
|
||||
else
|
||||
self.threadActivityText = [NSString stringWithFormat:@"Installed %zu bundles.", bundles.size()];
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void background_task_t::handle_reply (bool result)
|
||||
{
|
||||
delete this;
|
||||
}
|
||||
}
|
||||
|
||||
@implementation BundlesManager
|
||||
+ (BundlesManager*)sharedInstance
|
||||
{
|
||||
@@ -132,6 +37,8 @@ namespace
|
||||
{
|
||||
if(self = [super init])
|
||||
{
|
||||
self.progress = 1;
|
||||
|
||||
sourceList = bundles_db::sources();
|
||||
bundlesIndex = bundles_db::index(kInstallDirectory);
|
||||
|
||||
@@ -153,7 +60,6 @@ namespace
|
||||
if(_autoUpdateBundles = flag)
|
||||
{
|
||||
[self updateSources:nil];
|
||||
self.updateTimer = [NSTimer scheduledTimerWithTimeInterval:4*60*60 target:self selector:@selector(updateSources:) userInfo:nil repeats:YES];
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -162,122 +68,144 @@ namespace
|
||||
}
|
||||
}
|
||||
|
||||
- (void)updateProgress:(NSTimer*)aTimer
|
||||
- (void)installBundles:(std::vector<bundles_db::bundle_ptr> const&)bundlesReference completionHandler:(void(^)(std::vector<bundles_db::bundle_ptr> const& failedBundles))callback
|
||||
{
|
||||
self.progress = self.threadProgress;
|
||||
self.activityText = self.threadActivityText;
|
||||
__block std::vector<bundles_db::bundle_ptr> failedBundles;
|
||||
if(bundlesReference.empty())
|
||||
{
|
||||
callback(failedBundles);
|
||||
return;
|
||||
}
|
||||
|
||||
auto bundles = bundlesReference;
|
||||
for(auto bundle : bundles)
|
||||
installing.insert(bundle->uuid());
|
||||
|
||||
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
|
||||
dispatch_apply(bundles.size(), dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^(size_t i){
|
||||
D(DBF_BundlesManager, bug("Updating ‘%s’…\n", bundles[i]->name().c_str()););
|
||||
if(!bundles_db::update(bundles[i]))
|
||||
failedBundles.push_back(bundles[i]);
|
||||
});
|
||||
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
for(auto bundle : failedBundles)
|
||||
fprintf(stderr, "*** error downloading ‘%s’\n", bundle->url().c_str());
|
||||
for(auto bundle : bundles)
|
||||
installing.erase(bundle->uuid());
|
||||
callback(failedBundles);
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:BundlesManagerBundlesDidChangeNotification object:self];
|
||||
bundles_db::save_index(bundlesIndex, kInstallDirectory);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
- (void)didStartThreadActivity:(id)sender
|
||||
- (void)updateInstalledBundles
|
||||
{
|
||||
if(++scheduledTasks == 1)
|
||||
std::vector<bundles_db::bundle_ptr> bundles;
|
||||
for(auto installedBundle : bundlesIndex)
|
||||
{
|
||||
self.progressTimer = [NSTimer scheduledTimerWithTimeInterval:0.04 target:self selector:@selector(updateProgress:) userInfo:nil repeats:YES];
|
||||
self.isBusy = YES;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)didFinishActivityForSources:(std::vector<bundles_db::source_ptr> const&)someSources bundles:(std::vector<bundles_db::bundle_ptr> const&)someBundles
|
||||
{
|
||||
if(--scheduledTasks == 0)
|
||||
{
|
||||
[self.progressTimer invalidate];
|
||||
[self updateProgress:nil];
|
||||
self.progressTimer = nil;
|
||||
self.progress = 0;
|
||||
self.isBusy = NO;
|
||||
|
||||
[self performSelector:@selector(setActivityText:) withObject:nil afterDelay:5];
|
||||
}
|
||||
|
||||
if(!someSources.empty())
|
||||
{
|
||||
bundlesIndex = bundles_db::index(kInstallDirectory);
|
||||
if(![[NSUserDefaults standardUserDefaults] boolForKey:kUserDefaultsDisableBundleUpdatesKey])
|
||||
[self updateBundles:nil];
|
||||
}
|
||||
else if(!someBundles.empty())
|
||||
{
|
||||
iterate(bundle, someBundles)
|
||||
installing.erase((*bundle)->uuid());
|
||||
bundles_db::save_index(bundlesIndex, kInstallDirectory);
|
||||
}
|
||||
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:BundlesManagerBundlesDidChangeNotification object:self];
|
||||
}
|
||||
|
||||
- (void)updateSources:(id)sender
|
||||
{
|
||||
std::vector<bundles_db::source_ptr> sources;
|
||||
iterate(source, sourceList)
|
||||
{
|
||||
static double const kPollInterval = 3*60*60;
|
||||
if(!(*source)->disabled() && (*source)->needs_update(kPollInterval))
|
||||
sources.push_back(*source);
|
||||
}
|
||||
|
||||
if(!sources.empty())
|
||||
{
|
||||
if(network::can_reach_host("api.textmate.org"))
|
||||
new background_task_t(sources, std::vector<bundles_db::bundle_ptr>(), self, &_threadProgress);
|
||||
}
|
||||
else
|
||||
{
|
||||
NSDate* earliest = [NSDate distantFuture];
|
||||
iterate(source, sourceList)
|
||||
{
|
||||
NSDate* date = (NSDate*)CFBridgingRelease(CFDateCreate(kCFAllocatorDefault, (*source)->last_check().value()));
|
||||
earliest = [date earlierDate:earliest];
|
||||
}
|
||||
self.activityText = [NSString stringWithFormat:@"Last check: %@", [earliest humanReadableTimeElapsed]];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)updateBundles:(id)sender
|
||||
{
|
||||
std::set<bundles_db::bundle_ptr> bundles;
|
||||
iterate(installedBundle, bundlesIndex)
|
||||
{
|
||||
if(!(*installedBundle)->has_update())
|
||||
if(!installedBundle->has_update())
|
||||
continue;
|
||||
|
||||
citerate(bundle, bundles_db::dependencies(bundlesIndex, *installedBundle, false, false))
|
||||
for(auto bundle : bundles_db::dependencies(bundlesIndex, installedBundle, false, false))
|
||||
{
|
||||
if((*bundle)->has_update() || !(*bundle)->installed())
|
||||
bundles.insert(*bundle);
|
||||
if((bundle->has_update() || !bundle->installed()) && installing.find(bundle->uuid()) == installing.end())
|
||||
bundles.push_back(bundle);
|
||||
}
|
||||
}
|
||||
|
||||
if(!bundles.empty())
|
||||
new background_task_t(std::vector<bundles_db::source_ptr>(), std::vector<bundles_db::bundle_ptr>(bundles.begin(), bundles.end()), self, &_threadProgress);
|
||||
{
|
||||
self.activityText = @"Updating bundles…";
|
||||
self.isBusy = YES;
|
||||
[self installBundles:bundles completionHandler:^(std::vector<bundles_db::bundle_ptr> const& failedBundles){
|
||||
self.activityText = @"Bundles updated";
|
||||
self.isBusy = NO;
|
||||
}];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)updateSources:(id)sender
|
||||
{
|
||||
D(DBF_BundlesManager, bug("busy %s\n", BSTR(self.isBusy)););
|
||||
if(self.isBusy || sourceList.empty())
|
||||
return;
|
||||
|
||||
std::vector<bundles_db::source_ptr> sources;
|
||||
NSDate* earliest = [NSDate distantFuture];
|
||||
for(auto source : sourceList)
|
||||
{
|
||||
if(!source->disabled() && source->needs_update(kPollInterval))
|
||||
sources.push_back(source);
|
||||
earliest = [earliest earlierDate:CFBridgingRelease(CFDateCreate(kCFAllocatorDefault, source->last_check().value()))];
|
||||
}
|
||||
|
||||
if(sources.empty())
|
||||
{
|
||||
[self.updateTimer invalidate];
|
||||
self.updateTimer = [NSTimer scheduledTimerWithTimeInterval:MIN(kPollInterval + [earliest timeIntervalSinceNow], kPollInterval) target:self selector:@selector(updateSources:) userInfo:nil repeats:NO];
|
||||
self.activityText = [NSString stringWithFormat:@"Last check: %@", [earliest humanReadableTimeElapsed]];
|
||||
return;
|
||||
}
|
||||
|
||||
self.activityText = @"Updating sources…";
|
||||
self.isBusy = YES;
|
||||
|
||||
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
|
||||
__block std::vector<bundles_db::source_ptr> failedSources;
|
||||
dispatch_apply(sources.size(), dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^(size_t i){
|
||||
D(DBF_BundlesManager, bug("Updating ‘%s’…\n", sources[i]->url().c_str()););
|
||||
if(!bundles_db::update(sources[i]))
|
||||
failedSources.push_back(sources[i]);
|
||||
});
|
||||
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
for(auto source : failedSources)
|
||||
fprintf(stderr, "*** error downloading ‘%s’\n", source->url().c_str());
|
||||
|
||||
bundlesIndex = bundles_db::index(kInstallDirectory);
|
||||
// trigger reload for table view in Preferences → Bundles
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:BundlesManagerBundlesDidChangeNotification object:self];
|
||||
|
||||
self.activityText = @"Sources updated";
|
||||
self.isBusy = NO;
|
||||
|
||||
if(![[NSUserDefaults standardUserDefaults] boolForKey:kUserDefaultsDisableBundleUpdatesKey])
|
||||
[self updateInstalledBundles];
|
||||
|
||||
[self.updateTimer invalidate];
|
||||
self.updateTimer = [NSTimer scheduledTimerWithTimeInterval:(failedSources.empty() ? kPollInterval : 20*60*60) target:self selector:@selector(updateSources:) userInfo:nil repeats:NO];
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
- (void)installBundle:(bundles_db::bundle_ptr const&)aBundle
|
||||
{
|
||||
std::vector<bundles_db::bundle_ptr> bundles;
|
||||
citerate(bundle, bundles_db::dependencies(bundlesIndex, aBundle, true, false))
|
||||
for(auto bundle : bundles_db::dependencies(bundlesIndex, aBundle, true, false))
|
||||
{
|
||||
if(installing.find((*bundle)->uuid()) == installing.end())
|
||||
if(installing.find(bundle->uuid()) == installing.end())
|
||||
{
|
||||
bundles.push_back(*bundle);
|
||||
installing.insert((*bundle)->uuid());
|
||||
bundles.push_back(bundle);
|
||||
installing.insert(bundle->uuid());
|
||||
}
|
||||
}
|
||||
|
||||
if(!bundles.empty())
|
||||
{
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:BundlesManagerBundlesDidChangeNotification object:self];
|
||||
new background_task_t(std::vector<bundles_db::source_ptr>(), bundles, self, &_threadProgress);
|
||||
}
|
||||
NSString* bundleName = [NSString stringWithCxxString:aBundle->name()];
|
||||
self.activityText = [NSString stringWithFormat:@"Installing ‘%@’…", bundleName];
|
||||
self.isBusy = YES;
|
||||
[self installBundles:bundles completionHandler:^(std::vector<bundles_db::bundle_ptr> const& failedBundles){
|
||||
self.activityText = [NSString stringWithFormat:@"Installed ‘%@’.", bundleName];
|
||||
self.isBusy = NO;
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)uninstallBundle:(bundles_db::bundle_ptr const&)aBundle
|
||||
{
|
||||
bundles_db::uninstall(aBundle);
|
||||
bundles_db::save_index(bundlesIndex, kInstallDirectory);
|
||||
|
||||
self.activityText = [NSString stringWithFormat:@"Uninstalled ‘%s’.", aBundle->name().c_str()];
|
||||
[self performSelector:@selector(setActivityText:) withObject:nil afterDelay:5];
|
||||
self.activityText = [NSString stringWithFormat:@"Uninstalled ‘%@’.", [NSString stringWithCxxString:aBundle->name()]];
|
||||
}
|
||||
|
||||
- (NSCellStateValue)installStateForBundle:(bundles_db::bundle_ptr const&)aBundle
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
SOURCES = src/*.{cc,mm}
|
||||
LINK += io text plist regexp bundles updater network OakFoundation
|
||||
LINK += io text plist regexp bundles updater OakFoundation
|
||||
EXPORT = src/BundlesManager.h
|
||||
FRAMEWORKS = Foundation
|
||||
|
||||
Reference in New Issue
Block a user