//--- Methods --- protected override Yield Start(XDoc config, Result result) { yield return(Coroutine.Invoke(base.Start, config, new Result())); _varnish = Plug.New(Config["uri.varnish"].AsUri); _deki = Plug.New(Config["uri.deki"].AsUri); _apikey = Config["apikey"].AsText; _delayPurgeTimespan = TimeSpan.FromSeconds(config["varnish-purge-delay"].AsInt ?? 10); var dispatcher = new UpdateRecordDispatcher(OnQueueExpire); _updateDelayQueue = new UpdateDelayQueue(_delayPurgeTimespan, dispatcher); // set up subscription for pubsub XDoc subscriptionSet = new XDoc("subscription-set") .Elem("uri.owner", Self.Uri) .Start("subscription") .Add(DreamCookie.NewSetCookie("service-key", InternalAccessKey, Self.Uri).AsSetCookieDocument) .Elem("channel", "event://*/deki/pages/create") .Elem("channel", "event://*/deki/pages/move") .Elem("channel", "event://*/deki/pages/update") .Elem("channel", "event://*/deki/pages/delete") .Elem("channel", "event://*/deki/pages/revert") .Elem("channel", "event://*/deki/pages/createalias") .Elem("channel", "event://*/deki/pages/tags/update") .Elem("channel", "event://*/deki/pages/dependentschanged/comments/create") .Elem("channel", "event://*/deki/pages/dependentschanged/comments/update") .Elem("channel", "event://*/deki/pages/dependentschanged/comments/delete") .Elem("channel", "event://*/deki/pages/dependentschanged/files/create") .Elem("channel", "event://*/deki/pages/dependentschanged/files/update") .Elem("channel", "event://*/deki/pages/dependentschanged/files/delete") .Elem("channel", "event://*/deki/pages/dependentschanged/files/move") .Elem("channel", "event://*/deki/pages/dependentschanged/files/restore") .Elem("channel", "event://*/deki/files/create") .Elem("channel", "event://*/deki/files/update") .Elem("channel", "event://*/deki/files/delete") .Elem("channel", "event://*/deki/files/move") .Elem("channel", "event://*/deki/files/restore") .Start("recipient") .Attr("authtoken", _apikey) .Elem("uri", Self.Uri.At("queue")) .End() .End(); Result <DreamMessage> subscriptionResult; yield return(subscriptionResult = PubSub.At("subscribers").PostAsync(subscriptionSet)); string accessKey = subscriptionResult.Value.ToDocument()["access-key"].AsText; XUri location = subscriptionResult.Value.Headers.Location; Cookies.Update(DreamCookie.NewSetCookie("access-key", accessKey, location), null); _subscriptionLocation = location.AsLocalUri().WithoutQuery(); _log.DebugFormat("subscribed VarnishPurgeService for events at {0}", _subscriptionLocation); result.Return(); }
//--- Methods --- protected override Yield Start(XDoc config, Result result) { yield return(Coroutine.Invoke(base.Start, config, new Result())); // set up cache reaper _ttl = TimeSpan.FromSeconds(Config["news-ttl"].AsDouble ?? 60 * 60); double?checkInterval = Config["check-interval"].AsDouble; if (checkInterval.HasValue) { _checkInterval = TimeSpan.FromSeconds(checkInterval.Value); } else { double checkInterval2 = _ttl.TotalSeconds / 10; if (_ttl.TotalSeconds < 30 || checkInterval2 < 30) { checkInterval2 = 30; } _checkInterval = TimeSpan.FromSeconds(checkInterval2); } TaskTimer.New(_ttl, delegate(TaskTimer timer) { lock (_pageViews) { View next; do { if (_pageViews.Count == 0) { break; } next = _pageViews.Peek(); if (next != null) { if (next.Time.Add(_ttl) < DateTime.UtcNow) { _pageViews.Dequeue(); } else { break; } } } while(next != null); } timer.Change(_checkInterval, TaskEnv.None); }, null, TaskEnv.None); // get the apikey, which we will need as a subscription auth token for subscriptions not done on behalf of a user _apikey = config["apikey"].AsText; // build ignore list string ignore = config["ignore"].AsText; if (!string.IsNullOrEmpty(ignore)) { foreach (string page in ignore.Split(new char[] { ',', ' ' }, StringSplitOptions.RemoveEmptyEntries)) { _ignore[page] = null; } } // set up subscription for page views XDoc subscription = new XDoc("subscription-set") .Elem("uri.owner", Self.Uri.AsServerUri().ToString()) .Start("subscription") .Elem("channel", "event://*/deki/pages/view") .Add(DreamCookie.NewSetCookie("service-key", InternalAccessKey, Self.Uri).AsSetCookieDocument) .Start("recipient") .Attr("authtoken", _apikey) .Elem("uri", Self.Uri.AsServerUri().At("notify", "view").ToString()) .End() .End(); Result <DreamMessage> subscribe; yield return(subscribe = PubSub.At("subscribers").PostAsync(subscription)); string accessKey = subscribe.Value.ToDocument()["access-key"].AsText; XUri location = subscribe.Value.Headers.Location; Cookies.Update(DreamCookie.NewSetCookie("access-key", accessKey, location), null); _subscriptionLocation = Plug.New(location.AsLocalUri().WithoutQuery()); _log.DebugFormat("set up initial subscription location at {0}", _subscriptionLocation.Uri); result.Return(); }
protected override Yield Start(XDoc config, IContainer container, Result result) { yield return(Coroutine.Invoke(base.Start, config, new Result())); // ensure imagemagick is setup correctly. if (string.IsNullOrEmpty(ImageMagickConvertPath)) { throw new NotImplementedException("Please set 'imagemagick-convert-path' in config to path of ImageMagick's 'convert'"); } if (!File.Exists(ImageMagickIdentifyPath)) { throw new FileNotFoundException("Cannot find ImagicMagick 'identify' binary: ", ImageMagickIdentifyPath); } if (string.IsNullOrEmpty(ImageMagickIdentifyPath)) { throw new NotImplementedException("Please set 'imagemagick-identify-path' in config to path of ImageMagick's 'identify'"); } if (!File.Exists(ImageMagickConvertPath)) { throw new FileNotFoundException("Cannot find ImagicMagick 'convert' binary: ", ImageMagickConvertPath); } // check for 'apikey' _apikey = Config["api-key"].AsText ?? Config["apikey"].AsText; if (string.IsNullOrEmpty(_apikey)) { throw new ArgumentNullException("apikey", "The global apikey is not defined. Please ensure that you have a global <apikey> defined in the MindTouch Core service settings xml file."); } InitializeContainer(container); // intialize instance manager _instanceManager = InstanceManager.New(this, this.TimerFactory); // setup resource manager lock (SyncRoot) { if (ResourceManager == null) { ResourceManager = new PlainTextResourceManager(ResourcesPath); ScreenFont = new DekiFont(Plug.New("resource://mindtouch.deki/MindTouch.Deki.Resources.Arial.mtdf").Get().AsBytes()); } } // initialize scripting engine XDoc scripting = Config["scripting"]; DekiScriptLibrary.InsertTextLimit = scripting["max-web-response-length"].AsLong ?? DekiScriptLibrary.InsertTextLimit; DekiScriptLibrary.MinCacheTtl = scripting["min-web-cache-ttl"].AsDouble ?? DekiScriptLibrary.MinCacheTtl; // set up deki pub sub (by default we override uri.publish with our own service, unless @must-use=true is specified) if (!(Config["uri.publish/@must-use"].AsBool ?? false)) { Result <Plug> pubsubResult; XDoc pubsubConfig = new XDoc("config") .Elem("uri.deki", Self.Uri.With("apikey", MasterApiKey)) .Start("downstream") .Elem("uri", PubSub.At("publish").Uri.WithoutLastSegment().At("subscribers")) .End() .Start("components") .Start("component") .Attr("type", typeof(IPubSubDispatcher).AssemblyQualifiedName) .Attr("implementation", typeof(DekiDispatcher).AssemblyQualifiedName) .End() .End() .Elem("authtoken", MasterApiKey); foreach (var cookie in Cookies.Fetch(PubSub.Uri)) { pubsubConfig.Add(cookie.AsSetCookieDocument); } var messageQueuePath = config["publish/queue-path"].AsText; if (!string.IsNullOrEmpty(messageQueuePath)) { pubsubConfig.Elem("queue-path", messageQueuePath); } yield return(pubsubResult = CreateService( "pubsub", "sid://mindtouch.com/dream/2008/10/pubsub", pubsubConfig, new Result <Plug>())); PubSub = pubsubResult.Value; } // set up package updater service (unless it was passed in) XUri packageUpdater; if (config["packageupdater/@uri"].IsEmpty) { var packageConfig = config["packageupdater"]; packageConfig = packageConfig.IsEmpty ? new XDoc("config") : packageConfig.Clone(); if (packageConfig["package-path"].IsEmpty) { packageConfig.Elem("package-path", Path.Combine(Path.Combine(config["deki-path"].AsText, "packages"), "default")); } yield return(CreateService( "packageupdater", "sid://mindtouch.com/2010/04/packageupdater", new XDoc("config") .Elem("apikey", MasterApiKey) .AddNodes(packageConfig), new Result <Plug>() )); packageUpdater = Self.Uri.At("packageupdater"); } else { packageUpdater = config["packageupdater/@uri"].AsUri; } _packageUpdater = Plug.New(packageUpdater); // set up emailer service (unless it was passed in) XUri mailerUri; if (config["uri.mailer"].IsEmpty) { yield return(CreateService( "mailer", "sid://mindtouch.com/2009/01/dream/email", new XDoc("config") .Elem("apikey", MasterApiKey) .AddAll(Config["smtp/*"]), new Result <Plug>() )); mailerUri = Self.Uri.At("mailer"); } else { mailerUri = config["uri.mailer"].AsUri; } _mailer = Plug.New(mailerUri); // set up the email subscription service (unless it was passed in) XUri pageSubscription; if (config["uri.page-subscription"].IsEmpty) { XDoc pagesubserviceConfig = new XDoc("config") .Elem("uri.deki", Self.Uri) .Elem("uri.emailer", mailerUri.At("message")) .Elem("resources-path", ResourcesPath) .Elem("apikey", MasterApiKey) .Start("components") .Start("component") .Attr("scope", "factory") .Attr("type", typeof(IPageSubscriptionDataSessionFactory).AssemblyQualifiedName) .Attr("implementation", "MindTouch.Deki.Data.MySql.UserSubscription.MySqlPageSubscriptionSessionFactory, mindtouch.deki.data.mysql") .End() .End() .AddAll(Config["page-subscription/*"]); foreach (var cookie in Cookies.Fetch(mailerUri)) { pagesubserviceConfig.Add(cookie.AsSetCookieDocument); } yield return(CreateService( "pagesubservice", "sid://mindtouch.com/deki/2008/11/changesubscription", pagesubserviceConfig, new Result <Plug>() )); pageSubscription = Self.Uri.At("pagesubservice"); config.Elem("uri.page-subscription", pageSubscription); } else { pageSubscription = config["uri.page-subscription"].AsUri; } _pageSubscription = Plug.New(pageSubscription); // set up package importer, if not provided if (Config["uri.package"].IsEmpty) { yield return(CreateService( "package", "sid://mindtouch.com/2009/07/package", new XDoc("config").Elem("uri.deki", Self.Uri), new Result <Plug>())); Config.Elem("uri.package", Self.Uri.At("package")); } // set up lucene _luceneIndex = Plug.New(Config["indexer/@src"].AsUri); if (_luceneIndex == null) { // create the indexer service XDoc luceneIndexConfig = new XDoc("config") .AddNodes(Config["indexer"]) .Start("apikey").Attr("hidden", true).Value(MasterApiKey).End(); if (luceneIndexConfig["path.store"].IsEmpty) { luceneIndexConfig.Elem("path.store", Path.Combine(Path.Combine(config["deki-path"].AsText, "luceneindex"), "$1")); } yield return(CreateService("luceneindex", SID_FOR_LUCENE_INDEX, luceneIndexConfig, new Result <Plug>()).Set(v => _luceneIndex = v)); _isLocalLuceneService = true; } else { // push our host's pubsub service to lucene, to keep it up to date on our changes var pubsub = new XDoc("pubsub").Attr("href", PubSub); foreach (var cookie in PubSub.CookieJar.Fetch(PubSub.Uri)) { pubsub.Add(cookie.AsSetCookieDocument); } yield return(_luceneIndex.At("subscriptions").PostAsync(pubsub)); } // configure indexing whitelist _indexNamespaceWhitelist = new[] { NS.MAIN, NS.PROJECT, NS.USER, NS.TEMPLATE, NS.HELP, NS.MAIN_TALK, NS.PROJECT_TALK, NS.USER_TALK, NS.TEMPLATE_TALK, NS.HELP_TALK, NS.SPECIAL, NS.SPECIAL_TALK }; if (!string.IsNullOrEmpty(Config["indexer/namespace-whitelist"].AsText)) { List <NS> customWhitelist = new List <NS>(); foreach (string item in Config["indexer/namespace-whitelist"].AsText.Split(',')) { NS ns; if (SysUtil.TryParseEnum(item, out ns)) { customWhitelist.Add(ns); } } _indexNamespaceWhitelist = customWhitelist.ToArray(); } if (!Config["wikis/globalconfig/cache/varnish"].IsEmpty) { // create the varnish service // TODO (petee): getting the varnish config from wikis/globalconfig/cache is a hack // The frontend needs to get the max-age to send out the cache headers but we currently have no way // of getting the DekiWikiService config so we'll hack it so it comes back in GET:site/settings. XDoc varnishConfig = new XDoc("config") .Elem("uri.deki", Self.Uri.With("apikey", MasterApiKey)) .Elem("uri.varnish", Config["wikis/globalconfig/cache/varnish"].AsUri) .Elem("varnish-purge-delay", Config["wikis/globalconfig/cache/varnish-purge-delay"].AsInt ?? 10) .Elem("varnish-max-age", Config["wikis/globalconfig/cache/varnish-max-age"].AsInt ?? 300) .Start("apikey").Attr("hidden", true).Value(MasterApiKey).End(); yield return(CreateService("varnish", SID_FOR_VARNISH_SERVICE, varnishConfig, new Result <Plug>())); } _isInitialized = true; result.Return(); }
//--- Methods --- protected override Yield Start(XDoc config, Result result) { yield return(Coroutine.Invoke(base.Start, config, new Result())); // get the apikey, which we will need as a subscription auth token for subscriptions not done on behalf of a user _apikey = config["apikey"].AsText; // capture the wikiId of the wiki starting us string wikiId = config["wikiid"].AsText ?? "default"; // set up plug deki, so we can validate users XUri dekiUri = config["uri.deki"].AsUri ?? new XUri("http://localhost:8081/deki"); _deki = Plug.New(dekiUri).With("apikey", _apikey).WithHeader("X-Deki-Site", "id=" + wikiId); // get ajax polling interval _pollInterval = TimeSpan.FromSeconds(config["poll-interval"].AsDouble ?? 60); // set up subscription reaper TaskTimer.New(TimeSpan.FromSeconds(60), timer => { lock (_subscriptions) { var staleSubs = new List <uint>(); foreach (KeyValuePair <uint, Subscription> pair in _subscriptions) { if (pair.Value.LastTouched.Add(_pollInterval).Add(TimeSpan.FromSeconds(10)) < DateTime.UtcNow) { staleSubs.Add(pair.Key); } } foreach (uint pageId in staleSubs) { _log.DebugFormat("removing subscription for {0}", pageId); _subscriptions.Remove(pageId); } } timer.Change(TimeSpan.FromSeconds(60), TaskEnv.None); }, null, TaskEnv.None); // set up subscription for pubsub XDoc subscription = new XDoc("subscription-set") .Elem("uri.owner", Self.Uri.AsServerUri().ToString()) .Start("subscription") .Elem("channel", string.Format("event://{0}/deki/pages/update", wikiId)) .Elem("channel", string.Format("event://{0}/deki/pages/revert", wikiId)) .Elem("channel", string.Format("event://{0}/deki/pages/tags/update", wikiId)) .Elem("channel", string.Format("event://{0}/deki/pages/dependentschanged/comments/create", wikiId)) .Elem("channel", string.Format("event://{0}/deki/pages/dependentschanged/comments/update", wikiId)) .Elem("channel", string.Format("event://{0}/deki/pages/dependentschanged/comments/delete", wikiId)) .Elem("channel", string.Format("event://{0}/deki/pages/dependentschanged/files/create", wikiId)) .Elem("channel", string.Format("event://{0}/deki/pages/dependentschanged/files/update", wikiId)) .Elem("channel", string.Format("event://{0}/deki/pages/dependentschanged/files/delete", wikiId)) .Elem("channel", string.Format("event://{0}/deki/pages/dependentschanged/files/properties/*", wikiId)) .Elem("channel", string.Format("event://{0}/deki/pages/dependentschanged/files/restore", wikiId)) .Add(DreamCookie.NewSetCookie("service-key", InternalAccessKey, Self.Uri).AsSetCookieDocument) .Start("recipient") .Attr("authtoken", _apikey) .Elem("uri", Self.Uri.AsServerUri().At("notify").ToString()) .End() .End(); Result <DreamMessage> subscribe; yield return(subscribe = PubSub.At("subscribers").PostAsync(subscription)); string accessKey = subscribe.Value.ToDocument()["access-key"].AsText; XUri location = subscribe.Value.Headers.Location; Cookies.Update(DreamCookie.NewSetCookie("access-key", accessKey, location), null); _subscriptionLocation = Plug.New(location.AsLocalUri().WithoutQuery()); _log.DebugFormat("set up initial subscription location at {0}", _subscriptionLocation.Uri); result.Return(); }
//--- Methods --- protected override Yield Start(XDoc config, IContainer container, Result result) { yield return(Coroutine.Invoke(base.Start, config, new Result())); // set up plug for phpscript that will handle the notifications _emailer = Plug.New(config["uri.emailer"].AsUri); // set up plug deki, so we can validate users _deki = Plug.New(config["uri.deki"].AsUri); // get the apikey, which we will need as a subscription auth token for subscriptions not done on behalf of a user _apikey = config["apikey"].AsText; _cache = new PageChangeCache(_deki.With("apikey", _apikey), TimeSpan.FromSeconds(config["page-cache-ttl"].AsInt ?? 2)); if (!container.IsRegistered <IPageSubscriptionInstance>()) { var builder = new ContainerBuilder(); builder.Register <PageSubscriptionInstance>().As <IPageSubscriptionInstance>().FactoryScoped(); builder.Build(container); } // TODO (arnec): this should be hitting the API to retrieve resources // resource manager for email template var resourcePath = Config["resources-path"].AsText; if (!string.IsNullOrEmpty(resourcePath)) { _resourceManager = new PlainTextResourceManager(Environment.ExpandEnvironmentVariables(resourcePath)); } else { // creating a test resource manager _log.WarnFormat("'resource-path' was not defined in Config, using a test resource manager for email templating"); var testSet = new TestResourceSet { { "Notification.Page.email-subject", "Page Modified" }, { "Notification.Page.email-header", "The following pages have changed:" } }; _resourceManager = new PlainTextResourceManager(testSet); } // set up subscription for pubsub var subscriptionSet = new XDoc("subscription-set") .Elem("uri.owner", Self.Uri.AsServerUri().ToString()) .Start("subscription") .Elem("channel", "event://*/deki/users/*") .Add(DreamCookie.NewSetCookie("service-key", InternalAccessKey, Self.Uri).AsSetCookieDocument) .Start("recipient") .Attr("authtoken", _apikey) .Elem("uri", Self.Uri.AsServerUri().At("updateuser").ToString()) .End() .End() .Start("subscription") .Elem("channel", "event://*/deki/pages/create") .Elem("channel", "event://*/deki/pages/update") .Elem("channel", "event://*/deki/pages/delete") .Elem("channel", "event://*/deki/pages/revert") .Elem("channel", "event://*/deki/pages/move") .Elem("channel", "event://*/deki/pages/tags/update") .Elem("channel", "event://*/deki/pages/dependentschanged/comments/create") .Elem("channel", "event://*/deki/pages/dependentschanged/comments/update") .Elem("channel", "event://*/deki/pages/dependentschanged/comments/delete") .Elem("channel", "event://*/deki/pages/dependentschanged/files/create") .Elem("channel", "event://*/deki/pages/dependentschanged/files/update") .Elem("channel", "event://*/deki/pages/dependentschanged/files/delete") .Elem("channel", "event://*/deki/pages/dependentschanged/files/properties/*") .Elem("channel", "event://*/deki/pages/dependentschanged/files/restore") .Add(DreamCookie.NewSetCookie("service-key", InternalAccessKey, Self.Uri).AsSetCookieDocument) .Start("recipient") .Attr("authtoken", _apikey) .Elem("uri", Self.Uri.AsServerUri().At("notify").ToString()) .End() .End(); Result <DreamMessage> subscribe; yield return(subscribe = PubSub.At("subscribers").PostAsync(subscriptionSet)); string accessKey = subscribe.Value.ToDocument()["access-key"].AsText; XUri location = subscribe.Value.Headers.Location; Cookies.Update(DreamCookie.NewSetCookie("access-key", accessKey, location), null); _subscriptionLocation = Plug.New(location.AsLocalUri().WithoutQuery()); _log.DebugFormat("set up initial subscription location at {0}", _subscriptionLocation.Uri); // set up notification accumulator queue TimeSpan accumulationMinutes = TimeSpan.FromSeconds(config["accumulation-time"].AsInt ?? 10 * 60); _log.DebugFormat("Initializing queue with {0:0.00} minute accumulation", accumulationMinutes.TotalMinutes); _notificationQueue = new NotificationDelayQueue(accumulationMinutes, SendEmail); result.Return(); }