public void UserInfo_retrieve_subscription_for_a_page() { SubscriptionManager subscriptionManager = new SubscriptionManager(null, null); UserInfo userInfo1 = subscriptionManager.GetUser("a", 1, true); userInfo1.AddResource(10, "infinity"); userInfo1.AddResource(11, "0"); userInfo1.AddResource(12, "0"); userInfo1.AddResource(13, "infinity"); List<uint> pages = new List<uint>(); pages.Add(10); pages.Add(12); pages.Add(14); pages.Add(16); XDoc subscriptions = userInfo1.GetSubscriptionDoc(pages); Assert.AreEqual(2, subscriptions["subscription.page"].ListLength); XDoc page10 = subscriptions["subscription.page[@id='10']"]; Assert.IsFalse(page10.IsEmpty); Assert.AreEqual("infinity", page10["@depth"].AsText); XDoc page12 = subscriptions["subscription.page[@id='12']"]; Assert.IsFalse(page12.IsEmpty); Assert.AreEqual("0", page12["@depth"].AsText); }
protected override Yield Stop(Result result) { yield return _subscriptionLocation.DeleteAsync().Catch(); _subscriptions.RecordsChanged -= PersistSubscriptions; _subscriptions.SubscriptionsChanged -= PushSubscriptionSetUpstream; _subscriptions = null; _wikiLanguageCache.Clear(); yield return Coroutine.Invoke(base.Stop, new Result()); result.Return(); }
//--- Methods --- protected override Yield Start(XDoc config, 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)); // resource manager for email template string 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"); TestResourceSet testSet = new TestResourceSet(); testSet.Add("Notification.Page.email-subject", "Page Modified"); testSet.Add("Notification.Page.email-header", "The following pages have changed:"); _resourceManager = new PlainTextResourceManager(testSet); } // get persisted subscription storage List<Tuplet<string, List<XDoc>>> allWikiSubs = new List<Tuplet<string, List<XDoc>>>(); Result<DreamMessage> storageCatalog; yield return storageCatalog = Storage.At("subscriptions").GetAsync(); foreach(XDoc wikiSubs in storageCatalog.Value.ToDocument()["folder/name"]) { string wikihost = wikiSubs.AsText; Tuplet<string, List<XDoc>> wikiDoc = new Tuplet<string, List<XDoc>>(wikihost, new List<XDoc>()); allWikiSubs.Add(wikiDoc); Result<DreamMessage> wikiUsers; yield return wikiUsers = Storage.At("subscriptions", wikihost).GetAsync(); foreach(XDoc userDocname in wikiUsers.Value.ToDocument()["file/name"]) { string userFile = userDocname.AsText; if(!userFile.EndsWith(".xml")) { _log.WarnFormat("Found stray file '{0}' in wiki '{1}' store, ignoring", userFile, wikihost); continue; } Result<DreamMessage> userDoc; yield return userDoc = Storage.At("subscriptions", wikihost, userFile).GetAsync(); try { wikiDoc.Item2.Add(userDoc.Value.ToDocument()); } catch(InvalidDataException e) { _log.Error(string.Format("Unable to retrieve subscription store for user {0}/{1}", wikihost, userFile), e); } } } _subscriptions = new SubscriptionManager(Self.Uri.AsServerUri().At("notify"), allWikiSubs); _subscriptions.RecordsChanged += PersistSubscriptions; _subscriptions.SubscriptionsChanged += PushSubscriptionSetUpstream; // set up subscription for pubsub _baseSubscriptionSet = 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(); XDoc subSet = _baseSubscriptionSet.Clone(); foreach(XDoc sub in _subscriptions.Subscriptions) { subSet.Add(sub); } Result<DreamMessage> subscribe; yield return subscribe = PubSub.At("subscribers").PostAsync(subSet); 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(); }
public void SubscriptionManager_with_initial_subscriptions() { List<Tuplet<string, List<XDoc>>> subs = new List<Tuplet<string, List<XDoc>>>(); List<XDoc> x = new List<XDoc>(); Tuplet<string, List<XDoc>> xSubs = new Tuplet<string, List<XDoc>>("x", x); subs.Add(xSubs); x.Add(new XDoc("user") .Attr("userid", 1) .Elem("email", "foo") .Start("subscription.page").Attr("id", 1).Attr("depth", 0).End()); x.Add(new XDoc("user") .Attr("userid", 2) .Elem("email", "foo") .Start("subscription.page").Attr("id", 1).Attr("depth", 0).End() .Start("subscription.page").Attr("id", 2).Attr("depth", 0).End()); x.Add(new XDoc("user") .Attr("userid", 3) .Elem("email", "foo") .Start("subscription.page").Attr("id", 2).Attr("depth", 0).End()); List<XDoc> y = new List<XDoc>(); Tuplet<string, List<XDoc>> ySubs = new Tuplet<string, List<XDoc>>("y", y); subs.Add(ySubs); y.Add(new XDoc("user") .Attr("userid", 10) .Elem("email", "foo") .Start("subscription.page").Attr("id", 1).Attr("depth", 0).End()); SubscriptionManager subscriptionManager = new SubscriptionManager(new XUri("test://"), subs); List<XDoc> subscriptions = new List<XDoc>(subscriptionManager.Subscriptions); Assert.AreEqual(3, subscriptions.Count); bool foundXa = false; bool foundXb = false; bool foundYa = false; foreach(XDoc sub in subscriptions) { switch(sub["channel"].AsText) { case "event://x/deki/pages/create": switch(sub["uri.resource"].AsText) { case "deki://x/pages/1#depth=0": Assert.AreEqual(2, sub["recipient"].ListLength); foundXa = true; break; case "deki://x/pages/2#depth=0": Assert.AreEqual(2, sub["recipient"].ListLength); foundXb = true; break; default: Assert.Fail("bad resource for deki X"); break; } break; case "event://y/deki/pages/create": if(sub["uri.resource"].AsText != "deki://y/pages/1#depth=0") { Assert.Fail("bad resource for deki Y"); } XDoc recipient = sub["recipient"]; Assert.AreEqual(1, recipient.ListLength); Assert.AreEqual("10", recipient["@userid"].AsText); foundYa = true; break; } } Assert.IsTrue(foundXa); Assert.IsTrue(foundXb); Assert.IsTrue(foundYa); Assert.IsNotNull(subscriptionManager.GetUser("x", 1, false)); Assert.IsNotNull(subscriptionManager.GetUser("x", 2, false)); Assert.IsNotNull(subscriptionManager.GetUser("x", 3, false)); Assert.IsNull(subscriptionManager.GetUser("x", 10, false)); Assert.IsNotNull(subscriptionManager.GetUser("y", 10, false)); }
public void SubscriptionManager_user_subscription_management() { var subscriptionManager = new SubscriptionManager(null, null); var recordEvents = new List<RecordEventArgs>(); var recordsEvent = new ManualResetEvent(false); subscriptionManager.RecordsChanged += delegate(object sender, RecordEventArgs e) { recordEvents.Add(e); recordsEvent.Set(); }; SubscriptionEventArgs subscriptionEventArgs = null; var subscriptionsFired = 0; var subscriptionsEvent = new ManualResetEvent(false); subscriptionManager.SubscriptionsChanged += delegate(object sender, SubscriptionEventArgs e) { subscriptionEventArgs = e; subscriptionsFired++; subscriptionsEvent.Set(); }; _log.Debug("adding resource 1 to user 1"); recordsEvent.Reset(); subscriptionsEvent.Reset(); UserInfo userInfo1 = subscriptionManager.GetUser("a", 1, true); userInfo1.AddResource(1, "0"); userInfo1.Save(); _log.Debug("waiting on events"); Assert.IsTrue(recordsEvent.WaitOne(2000, true)); Assert.IsTrue(subscriptionsEvent.WaitOne(2000, true)); Assert.IsNotNull(subscriptionManager.GetUser("a", 1, false)); Assert.AreEqual(1, recordEvents.Count); Assert.AreEqual(1, subscriptionsFired); Assert.AreEqual("a", recordEvents[0].WikiId); Assert.AreEqual(1, recordEvents[0].User.Id); Assert.AreEqual(1, subscriptionEventArgs.Subscriptions.Length); Assert.AreEqual("deki://a/pages/1#depth=0", subscriptionEventArgs.Subscriptions[0]["uri.resource"].AsText); _log.Debug("adding resource 1 to user 2"); recordsEvent.Reset(); subscriptionsEvent.Reset(); UserInfo userInfo2 = subscriptionManager.GetUser("a", 2, true); userInfo2.AddResource(1, "0"); userInfo2.Save(); _log.Debug("waiting on events"); Assert.IsTrue(recordsEvent.WaitOne(2000, true)); Assert.IsTrue(subscriptionsEvent.WaitOne(2000, true)); Assert.IsNotNull(subscriptionManager.GetUser("a", 2, false)); Assert.AreEqual(2, recordEvents.Count); Assert.AreEqual(2, subscriptionsFired); Assert.AreEqual("a", recordEvents[1].WikiId); Assert.AreEqual(2, recordEvents[1].User.Id); Assert.AreEqual(1, subscriptionEventArgs.Subscriptions.Length); Assert.AreEqual("deki://a/pages/1#depth=0", subscriptionEventArgs.Subscriptions[0]["uri.resource"].AsText); Assert.AreEqual(2, subscriptionEventArgs.Subscriptions[0]["recipient"].ListLength); _log.Debug("adding resource 2 to user 2"); recordsEvent.Reset(); subscriptionsEvent.Reset(); UserInfo userInfo2a = subscriptionManager.GetUser("a", 2, false); userInfo2a.AddResource(2, "0"); userInfo2a.Save(); _log.Debug("waiting on events"); Assert.IsTrue(recordsEvent.WaitOne(2000, true)); Assert.IsTrue(subscriptionsEvent.WaitOne(2000, true)); UserInfo userInfo = subscriptionManager.GetUser("a", 2, false); Assert.AreEqual(2, userInfo.Resources.Length); Assert.AreEqual(3, recordEvents.Count); Assert.AreEqual(3, subscriptionsFired); Assert.AreEqual("a", recordEvents[2].WikiId); Assert.AreEqual(2, recordEvents[2].User.Id); Assert.AreEqual(2, subscriptionEventArgs.Subscriptions.Length); Assert.AreEqual("deki://a/pages/2#depth=0", subscriptionEventArgs.Subscriptions[1]["uri.resource"].AsText); Assert.AreEqual(1, subscriptionEventArgs.Subscriptions[1]["recipient"].ListLength); _log.Debug("removing resource 1 from user 1"); recordsEvent.Reset(); subscriptionsEvent.Reset(); UserInfo userInfo1a = subscriptionManager.GetUser("a", 1, false); userInfo1a.RemoveResource(1); userInfo1a.Save(); _log.Debug("waiting on events"); Assert.IsTrue(recordsEvent.WaitOne(2000, true)); Assert.IsTrue(subscriptionsEvent.WaitOne(2000, true)); Assert.IsNull(subscriptionManager.GetUser("a", 1, false)); Assert.AreEqual(4, subscriptionsFired); Assert.AreEqual(4, recordEvents.Count); Assert.AreEqual("a", recordEvents[3].WikiId); Assert.AreEqual(1, recordEvents[3].User.Id); Assert.AreEqual(2, subscriptionEventArgs.Subscriptions.Length); Assert.AreEqual("deki://a/pages/1#depth=0", subscriptionEventArgs.Subscriptions[0]["uri.resource"].AsText); Assert.AreEqual(1, subscriptionEventArgs.Subscriptions[0]["recipient"].ListLength); }
public void SubscriptionManager_collapses_subscriptions_to_favor_infinite_depth() { SubscriptionManager subscriptionManager = new SubscriptionManager(null, null); UserInfo userInfo = subscriptionManager.GetUser("a", 1, true); userInfo.AddResource(1, "0"); userInfo.AddResource(1, "infinity"); List<XDoc> subscriptions = new List<XDoc>(subscriptionManager.Subscriptions); Assert.AreEqual(1, subscriptions.Count); Assert.AreEqual("deki://a/pages/1#depth=infinity", subscriptions[0]["uri.resource"].AsText); }